|
7235
|
130
|
27
|
2026-04-13T14:55:01.049264+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776092101049_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
http://100.73.206.126:8766
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:37 ·
iTerm2
/ -zsh
AXTextArea
[accessibility]
...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD
Red
Plus
6TB price iTerm2 7 ⠂ Research Western Digital
Red
Plus
pricing iTerm2 7...
11:40 ·
iTerm2
/ -zsh
AXTextArea
[accessibility]
...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD
Red
Plus
6TB price iTerm2 7 ⠂ Research Western Digital
Red
Plus
pricing iTerm2 7...
11:45 ·
iTerm2
/ ⠂ Unable to access screenpipe activity data
AXTextArea
[accessibility]...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (9) - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Shameless • HBO Max","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shameless • HBO Max","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Settings","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"firefox sidebar - Google Search","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"firefox sidebar - Google Search","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"How to use AI-enhanced tab groups | Firefox Help","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"How to use AI-enhanced tab groups | Firefox Help","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons Manager","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons Manager","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"dennikn.sk/","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dennikn.sk/","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium Options","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium Options","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Machines - Tailscale","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Machines - Tailscale","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Browser Extension Getting Started | Bitwarden","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Browser Extension Getting Started | Bitwarden","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Extensions – Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Extensions – Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dangbei Atom Review - RTINGS.com","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dangbei Atom Review - RTINGS.com","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to Firefox","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Firefox","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Screenpipe","depth":7,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Red plus","depth":8,"value":"Red plus","help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Source","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"any","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Role","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"AXButton…","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"From","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"To","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)|","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD6OEFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Хард диск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.0,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.0,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.0,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"bounds":{"left":0.16388889,"top":0.017777778,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.018888889,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.018888889,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.018888889,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.04111111,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.04111111,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.04111111,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.37222221,"top":0.04111111,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.08555555,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.086666666,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.086666666,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.086666666,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.10888889,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.10888889,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.10888889,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.10888889,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.15333334,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.15444444,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.15444444,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.15444444,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.17666666,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.17666666,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.17666666,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.17666666,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2211111,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.22222222,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.22222222,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.22222222,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.24444444,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.24444444,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.24444444,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.24444444,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"( 3.5\", 256MB, 5400 RPM, SATA 6Gb/S)…..","depth":10,"bounds":{"left":0.34166667,"top":0.24444444,"width":0.18194444,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2888889,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.29,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.29,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.29,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.3122222,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.3122222,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.3122222,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.3122222,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(3.5\", 256MB, 5400 RPM, SATA 6Gb/S) ...","depth":10,"bounds":{"left":0.34166667,"top":0.3122222,"width":0.17708333,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.35666665,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.35777777,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.35777777,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.35777777,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.38,"width":0.51944447,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.6840278,"top":0.38,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.38,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.7222222,"top":0.38,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.42444444,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.42555556,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.42555556,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.42555556,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.44777778,"width":0.51944447,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.6840278,"top":0.44777778,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.44777778,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.7222222,"top":0.44777778,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:37 ·","depth":10,"bounds":{"left":0.16388889,"top":0.49222222,"width":0.025,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iTerm2","depth":11,"bounds":{"left":0.18888889,"top":0.49222222,"width":0.02638889,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/ -zsh","depth":10,"bounds":{"left":0.21527778,"top":0.49222222,"width":0.023611112,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AXTextArea","depth":10,"bounds":{"left":0.24652778,"top":0.49333334,"width":0.03888889,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[accessibility]","depth":10,"bounds":{"left":0.29097223,"top":0.49333334,"width":0.046527777,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD","depth":10,"bounds":{"left":0.16388889,"top":0.51555556,"width":0.2777778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.44236112,"top":0.51555556,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.46319443,"top":0.51555556,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB price iTerm2 7 ⠂ Research Western Digital","depth":10,"bounds":{"left":0.48125,"top":0.51555556,"width":0.2013889,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.68333334,"top":0.51555556,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.51555556,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"pricing iTerm2 7...","depth":10,"bounds":{"left":0.7222222,"top":0.51555556,"width":0.077083334,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:40 ·","depth":10,"bounds":{"left":0.16388889,"top":0.56,"width":0.025694445,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iTerm2","depth":11,"bounds":{"left":0.18958333,"top":0.56,"width":0.02638889,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/ -zsh","depth":10,"bounds":{"left":0.21597221,"top":0.56,"width":0.022916667,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AXTextArea","depth":10,"bounds":{"left":0.24652778,"top":0.5611111,"width":0.039583333,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[accessibility]","depth":10,"bounds":{"left":0.29166666,"top":0.5611111,"width":0.046527777,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD","depth":10,"bounds":{"left":0.16388889,"top":0.5833333,"width":0.2777778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.44236112,"top":0.5833333,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.46319443,"top":0.5833333,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB price iTerm2 7 ⠂ Research Western Digital","depth":10,"bounds":{"left":0.48125,"top":0.5833333,"width":0.2013889,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.68333334,"top":0.5833333,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.5833333,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"pricing iTerm2 7...","depth":10,"bounds":{"left":0.7222222,"top":0.5833333,"width":0.077083334,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:45 ·","depth":10,"bounds":{"left":0.16388889,"top":0.62777776,"width":0.025694445,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iTerm2","depth":11,"bounds":{"left":0.18958333,"top":0.62777776,"width":0.02638889,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/ ⠂ Unable to access screenpipe activity data","depth":10,"bounds":{"left":0.21597221,"top":0.62777776,"width":0.16597222,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AXTextArea","depth":10,"bounds":{"left":0.38958332,"top":0.6288889,"width":0.03888889,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[accessibility]","depth":10,"bounds":{"left":0.4340278,"top":0.6288889,"width":0.047222223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-3198097402588715975
|
8137806959598258288
|
click
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:37 ·
iTerm2
/ -zsh
AXTextArea
[accessibility]
...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD
Red
Plus
6TB price iTerm2 7 ⠂ Research Western Digital
Red
Plus
pricing iTerm2 7...
11:40 ·
iTerm2
/ -zsh
AXTextArea
[accessibility]
...New Tab iTerm2 7 ✳ Claude Code iTerm2 7 ⠂ Find cheapest WD
Red
Plus
6TB price iTerm2 7 ⠂ Research Western Digital
Red
Plus
pricing iTerm2 7...
11:45 ·
iTerm2
/ ⠂ Unable to access screenpipe activity data
AXTextArea
[accessibility]...
|
7234
|
|
7232
|
130
|
24
|
2026-04-13T14:54:54.973726+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776092094973_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
http://100.73.206.126:8766
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (9) - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Shameless • HBO Max","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shameless • HBO Max","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Settings","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"firefox sidebar - Google Search","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"firefox sidebar - Google Search","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"How to use AI-enhanced tab groups | Firefox Help","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"How to use AI-enhanced tab groups | Firefox Help","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons Manager","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons Manager","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"dennikn.sk/","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dennikn.sk/","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium Options","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium Options","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Machines - Tailscale","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Machines - Tailscale","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Browser Extension Getting Started | Bitwarden","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Browser Extension Getting Started | Bitwarden","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Extensions – Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Extensions – Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dangbei Atom Review - RTINGS.com","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dangbei Atom Review - RTINGS.com","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to Firefox","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Firefox","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Screenpipe","depth":7,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Red plus","depth":8,"value":"Red plus","help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Source","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"any","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Role","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"AXButton…","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"From","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"To","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)|","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD6OEFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Хард диск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.0,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.0,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.0,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"bounds":{"left":0.16388889,"top":0.017777778,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.018888889,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.018888889,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.018888889,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.04111111,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.04111111,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.04111111,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.37222221,"top":0.04111111,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.08555555,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.086666666,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.086666666,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.086666666,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.10888889,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.10888889,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.10888889,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.10888889,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.15333334,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.15444444,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.15444444,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.15444444,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.17666666,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.17666666,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.17666666,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.17666666,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2211111,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.22222222,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.22222222,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.22222222,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.24444444,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.24444444,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.24444444,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.24444444,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"( 3.5\", 256MB, 5400 RPM, SATA 6Gb/S)…..","depth":10,"bounds":{"left":0.34166667,"top":0.24444444,"width":0.18194444,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2888889,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.29,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.29,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.29,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.3122222,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.3122222,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.3122222,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.3122222,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(3.5\", 256MB, 5400 RPM, SATA 6Gb/S) ...","depth":10,"bounds":{"left":0.34166667,"top":0.3122222,"width":0.17708333,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.35666665,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.35777777,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.35777777,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.35777777,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.38,"width":0.51944447,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.6840278,"top":0.38,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.38,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.7222222,"top":0.38,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.42444444,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.42555556,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.42555556,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.42555556,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.44777778,"width":0.51944447,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.6840278,"top":0.44777778,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.70416665,"top":0.44777778,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.7222222,"top":0.44777778,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7374016967728260787
|
8137806959598127186
|
click
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)...
|
NULL
|
|
52580
|
1136
|
35
|
2026-04-20T07:23:23.805748+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669803805_m1.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases
releases
releases
releases
releases
releases
releases
Notes
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454)....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Events","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Events","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (6)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"fix(security): composer dependency updates – 2026-04-15","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":19,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@github-actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"github-actions bot commented 5 days ago •","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5 days ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Security dependency updates — composer — 2026-04-15","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security dependency updates — composer — 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"below.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CI run logs →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CI run logs →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Upgrade safety (changelog review)","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Upgrade safety (changelog review)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall verdict:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#463","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#463","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(phpunit/phpunit) is listed under","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Skipped alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This does not replace CI, tests, or manual smoke checks before merge.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Fixed alerts","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Covered by bump to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#425).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6304599685867586584
|
8132380052641688752
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases
releases
releases
releases
releases
releases
releases
Notes
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454)....
|
52574
|
|
52568
|
1137
|
42
|
2026-04-20T07:22:50.721473+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669770721_m2.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.09524601,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.15525267,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.06981383,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.10688165,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.12915559,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.014960106,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.06200133,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Events","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Events","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.030418882,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.2052859,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"bounds":{"left":0.083277926,"top":0.06943336,"width":0.026761968,"height":0.014764565},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"bounds":{"left":0.083277926,"top":0.06943336,"width":0.026761968,"height":0.014764565},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"bounds":{"left":0.1783577,"top":0.06424581,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"bounds":{"left":0.082446806,"top":0.09976058,"width":0.107546546,"height":0.025538707},"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (6)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"bounds":{"left":0.40641624,"top":0.0726257,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard","depth":14,"bounds":{"left":0.42503324,"top":0.058260176,"width":0.20162898,"height":0.042298485},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"fix(security): composer dependency updates – 2026-04-15","depth":16,"bounds":{"left":0.42503324,"top":0.05865922,"width":0.13397606,"height":0.01915403},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":17,"bounds":{"left":0.42503324,"top":0.06304868,"width":0.13397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"bounds":{"left":0.5616689,"top":0.06304868,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":16,"bounds":{"left":0.56449467,"top":0.06304868,"width":0.012466756,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":18,"bounds":{"left":0.42503324,"top":0.08339984,"width":0.03873005,"height":0.011971269},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":19,"bounds":{"left":0.42503324,"top":0.08339984,"width":0.03873005,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":18,"bounds":{"left":0.46509308,"top":0.08339984,"width":0.055352394,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"bounds":{"left":0.52177525,"top":0.08180367,"width":0.018284574,"height":0.015163607},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"bounds":{"left":0.52377,"top":0.083798885,"width":0.014295213,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"bounds":{"left":0.54138964,"top":0.08339984,"width":0.00880984,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":19,"bounds":{"left":0.5515292,"top":0.08180367,"width":0.061502658,"height":0.015163607},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":20,"bounds":{"left":0.55352396,"top":0.083798885,"width":0.057513297,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"bounds":{"left":0.6143617,"top":0.07821229,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@github-actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"github-actions bot commented 5 days ago •","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5 days ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Security dependency updates — composer — 2026-04-15","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security dependency updates — composer — 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"below.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CI run logs →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CI run logs →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Upgrade safety (changelog review)","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Upgrade safety (changelog review)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall verdict:","depth":18,"bounds":{"left":0.42004654,"top":0.0,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed","depth":18,"bounds":{"left":0.4554521,"top":0.0,"width":0.013464096,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert","depth":17,"bounds":{"left":0.46891624,"top":0.0,"width":0.16988032,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#463","depth":17,"bounds":{"left":0.63879657,"top":0.0,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#463","depth":18,"bounds":{"left":0.63879657,"top":0.0,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(phpunit/phpunit) is listed under","depth":17,"bounds":{"left":0.42004654,"top":0.0,"width":0.25116357,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Skipped alerts","depth":18,"bounds":{"left":0.4709109,"top":0.0,"width":0.032413565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.","depth":17,"bounds":{"left":0.42004654,"top":0.0,"width":0.2435173,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This does not replace CI, tests, or manual smoke checks before merge.","depth":18,"bounds":{"left":0.42004654,"top":0.0,"width":0.2534907,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Fixed alerts","depth":16,"bounds":{"left":0.42004654,"top":0.0047885077,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":17,"bounds":{"left":0.42004654,"top":0.00518755,"width":0.031416222,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"bounds":{"left":0.42486703,"top":0.050678372,"width":0.010970744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"bounds":{"left":0.4509641,"top":0.050678372,"width":0.018949468,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"bounds":{"left":0.48470744,"top":0.050678372,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"bounds":{"left":0.5270944,"top":0.050678372,"width":0.009474734,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"bounds":{"left":0.5611702,"top":0.042298485,"width":0.018450798,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"bounds":{"left":0.5894282,"top":0.050678372,"width":0.024102394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"bounds":{"left":0.6409575,"top":0.050678372,"width":0.013297873,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"bounds":{"left":0.4247008,"top":0.11971269,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"bounds":{"left":0.4247008,"top":0.11971269,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"bounds":{"left":0.44514626,"top":0.111332804,"width":0.018949468,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.11971269,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"bounds":{"left":0.5121343,"top":0.11971269,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"bounds":{"left":0.5121343,"top":0.11971269,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"bounds":{"left":0.56050533,"top":0.11971269,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.11971269,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.11971269,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.08619314,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"bounds":{"left":0.6225067,"top":0.10295291,"width":0.048537236,"height":0.04708699},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":0.15522745,"width":0.035738032,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":0.15323225,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"bounds":{"left":0.4247008,"top":0.22226655,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"bounds":{"left":0.4247008,"top":0.22226655,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"bounds":{"left":0.44514626,"top":0.21388668,"width":0.01861702,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.22226655,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"bounds":{"left":0.5121343,"top":0.22226655,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"bounds":{"left":0.5121343,"top":0.22226655,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"bounds":{"left":0.56050533,"top":0.22226655,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.22226655,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.22226655,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.18036711,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"bounds":{"left":0.6225067,"top":0.1971269,"width":0.048537236,"height":0.06384677},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":0.2661612,"width":0.035738032,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":0.264166,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"bounds":{"left":0.4247008,"top":0.32482043,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"bounds":{"left":0.4247008,"top":0.32482043,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.44514626,"top":0.31644055,"width":0.022273935,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.32482043,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"bounds":{"left":0.5121343,"top":0.32482043,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"bounds":{"left":0.5121343,"top":0.32482043,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"bounds":{"left":0.56050533,"top":0.32482043,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.32482043,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.32482043,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.29130086,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"bounds":{"left":0.6225067,"top":0.30806065,"width":0.048537236,"height":0.04708699},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"bounds":{"left":0.6225067,"top":0.35834,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"bounds":{"left":0.63248,"top":0.35834,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.4247008,"top":0.43575418,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.4247008,"top":0.43575418,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"bounds":{"left":0.44514626,"top":0.4273743,"width":0.02925532,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"bounds":{"left":0.48470744,"top":0.43575418,"width":0.017287234,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"bounds":{"left":0.5121343,"top":0.43575418,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"bounds":{"left":0.5121343,"top":0.43575418,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"bounds":{"left":0.56050533,"top":0.43575418,"width":0.011303191,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.43575418,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.43575418,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.38547486,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via","depth":20,"bounds":{"left":0.6225067,"top":0.40223464,"width":0.048537236,"height":0.08060654},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":0.48802873,"width":0.035738032,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":0.48603353,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"bounds":{"left":0.4247008,"top":0.54668796,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"bounds":{"left":0.4247008,"top":0.54668796,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.44514626,"top":0.5383081,"width":0.022273935,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"bounds":{"left":0.48470744,"top":0.54668796,"width":0.00731383,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"bounds":{"left":0.5121343,"top":0.54668796,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"bounds":{"left":0.5121343,"top":0.54668796,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"bounds":{"left":0.56050533,"top":0.54668796,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.54668796,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.54668796,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.5131684,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Covered by bump to 3.0.51 (also","depth":20,"bounds":{"left":0.6225067,"top":0.52992815,"width":0.048537236,"height":0.04708699},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"bounds":{"left":0.6225067,"top":0.5802075,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#425).","depth":20,"bounds":{"left":0.63248,"top":0.5802075,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"bounds":{"left":0.42486703,"top":0.050678372,"width":0.010970744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"bounds":{"left":0.4247008,"top":0.11971269,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"bounds":{"left":0.4247008,"top":0.11971269,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"bounds":{"left":0.4247008,"top":0.22226655,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"bounds":{"left":0.4247008,"top":0.22226655,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"bounds":{"left":0.4247008,"top":0.32482043,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"bounds":{"left":0.4247008,"top":0.32482043,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.4247008,"top":0.43575418,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.4247008,"top":0.43575418,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"bounds":{"left":0.4247008,"top":0.54668796,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"bounds":{"left":0.4247008,"top":0.54668796,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"bounds":{"left":0.4509641,"top":0.050678372,"width":0.018949468,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"bounds":{"left":0.44514626,"top":0.111332804,"width":0.018949468,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"bounds":{"left":0.44514626,"top":0.21388668,"width":0.01861702,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.44514626,"top":0.31644055,"width":0.022273935,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"bounds":{"left":0.44514626,"top":0.4273743,"width":0.02925532,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.44514626,"top":0.5383081,"width":0.022273935,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"bounds":{"left":0.48470744,"top":0.050678372,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.11971269,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.22226655,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.32482043,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"bounds":{"left":0.48470744,"top":0.43575418,"width":0.017287234,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"bounds":{"left":0.48470744,"top":0.54668796,"width":0.00731383,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"bounds":{"left":0.5270944,"top":0.050678372,"width":0.009474734,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"bounds":{"left":0.5121343,"top":0.11971269,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"bounds":{"left":0.5121343,"top":0.11971269,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"bounds":{"left":0.5121343,"top":0.22226655,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"bounds":{"left":0.5121343,"top":0.22226655,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"bounds":{"left":0.5121343,"top":0.32482043,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"bounds":{"left":0.5121343,"top":0.32482043,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8409162413310818289
|
8127894044931927474
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 1 commit into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935...
|
NULL
|
|
19441
|
413
|
36
|
2026-04-15T07:39:34.208077+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-15/1776 /Users/lukas/.screenpipe/data/data/2026-04-15/1776238774208_m1.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970/changes
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,550) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (29)
Pull requests
(
29
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (25)
Security and quality
(
25
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Preview
Preview
Awaiting approval
Awaiting approval
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (1)
Conversation
(
1
)
Commits (1)
Commits
(
1
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Pull Request Toolbar
Pull Request Toolbar
Collapse file tree
Open
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
All commits
All commits
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
0
/
1
viewed
Awaiting approval
Awaiting approval
Submit review
Submit
review
Open diff view settings
Open overview panel
Open comments panel
(
0
)
Filter files…
Filter options
File tree
File tree
composer.lock
composer.lock
Collapse file
composer.lock
composer.lock
composer.lock
Copy file name to clipboard
Expand all lines: composer.lock...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,550) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"For you - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (29)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (25)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Preview","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Preview","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Awaiting approval","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Pull Request Toolbar","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pull Request Toolbar","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse file tree","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Open","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"All commits","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All commits","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viewed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Awaiting approval","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Submit review","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Submit","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"review","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open diff view settings","depth":14,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open overview panel","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open comments panel","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"(","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Filter files…","depth":16,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Filter options","depth":16,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"File tree","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File tree","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"composer.lock","depth":19,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"composer.lock","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse file","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"composer.lock","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"composer.lock","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"composer.lock","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy file name to clipboard","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Expand all lines: composer.lock","depth":15,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-1036061935527444800
|
8121492036163223917
|
visual_change
|
accessibility
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,550) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (29)
Pull requests
(
29
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (25)
Security and quality
(
25
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Preview
Preview
Awaiting approval
Awaiting approval
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (1)
Conversation
(
1
)
Commits (1)
Commits
(
1
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Pull Request Toolbar
Pull Request Toolbar
Collapse file tree
Open
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
All commits
All commits
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
0
/
1
viewed
Awaiting approval
Awaiting approval
Submit review
Submit
review
Open diff view settings
Open overview panel
Open comments panel
(
0
)
Filter files…
Filter options
File tree
File tree
composer.lock
composer.lock
Collapse file
composer.lock
composer.lock
composer.lock
Copy file name to clipboard
Expand all lines: composer.lock...
|
19440
|
|
19430
|
413
|
30
|
2026-04-15T07:39:00.971330+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-15/1776 /Users/lukas/.screenpipe/data/data/2026-04-15/1776238740971_m1.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970/changes
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,550) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (29)
Pull requests
(
29
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (25)
Security and quality
(
25
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Preview
Preview
Awaiting approval
Awaiting approval
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (1)
Conversation
(
1
)
Commits (1)
Commits
(
1
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Pull Request Toolbar
Pull Request Toolbar
Collapse file tree
All commits
All commits
0
/
1
viewed
Submit review
Submit
review
Open diff view settings
Open overview panel
Open comments panel
(
0
)
Filter files…
Filter options
File tree
File tree
composer.lock
composer.lock
Collapse file
composer.lock
composer.lock...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,550) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"For you - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (29)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (25)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Preview","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Preview","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Awaiting approval","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Pull Request Toolbar","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pull Request Toolbar","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse file tree","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"All commits","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All commits","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viewed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Submit review","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Submit","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"review","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open diff view settings","depth":14,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open overview panel","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open comments panel","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"(","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Filter files…","depth":16,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Filter options","depth":16,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"File tree","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File tree","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"composer.lock","depth":19,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"composer.lock","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse file","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"composer.lock","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"composer.lock","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-3929942556044992050
|
8121491898724274529
|
visual_change
|
accessibility
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,550) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (29)
Pull requests
(
29
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (25)
Security and quality
(
25
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Preview
Preview
Awaiting approval
Awaiting approval
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (1)
Conversation
(
1
)
Commits (1)
Commits
(
1
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Pull Request Toolbar
Pull Request Toolbar
Collapse file tree
All commits
All commits
0
/
1
viewed
Submit review
Submit
review
Open diff view settings
Open overview panel
Open comments panel
(
0
)
Filter files…
Filter options
File tree
File tree
composer.lock
composer.lock
Collapse file
composer.lock
composer.lock...
|
19428
|
|
7230
|
130
|
22
|
2026-04-13T14:54:01.144871+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776092041144_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
http://100.73.206.126:8766
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (9) - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Shameless • HBO Max","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shameless • HBO Max","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Settings","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"firefox sidebar - Google Search","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"firefox sidebar - Google Search","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"How to use AI-enhanced tab groups | Firefox Help","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"How to use AI-enhanced tab groups | Firefox Help","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons Manager","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons Manager","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"dennikn.sk/","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dennikn.sk/","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium Options","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium Options","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Machines - Tailscale","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Machines - Tailscale","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Browser Extension Getting Started | Bitwarden","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Browser Extension Getting Started | Bitwarden","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Extensions – Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Extensions – Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dangbei Atom Review - RTINGS.com","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dangbei Atom Review - RTINGS.com","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to Firefox","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Firefox","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Screenpipe","depth":7,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Red plus","depth":8,"value":"Red plus","help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Source","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"any","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Role","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"AXButton…","depth":8,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"From","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"To","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"™","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB SATA 6Gb (WD60EFPX)|","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD6OEFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Хард диск WD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Western Digital","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.0,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.0,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.0,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.0,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.0,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.0,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3","depth":10,"bounds":{"left":0.37222221,"top":0.0,"width":0.14027777,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:38","depth":10,"bounds":{"left":0.16388889,"top":0.017777778,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.018888889,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.018888889,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.018888889,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Производител: Western Digital Модел:","depth":10,"bounds":{"left":0.16388889,"top":0.04111111,"width":0.16875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.33333334,"top":0.04111111,"width":0.016666668,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.35347223,"top":0.04111111,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)","depth":10,"bounds":{"left":0.37222221,"top":0.04111111,"width":0.19791667,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.08555555,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.086666666,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:1.00","depth":10,"bounds":{"left":0.21388888,"top":0.086666666,"width":0.03125,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24722221,"top":0.086666666,"width":0.016666668,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.10888889,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.10888889,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.10888889,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.10888889,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.15333334,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.15444444,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.15444444,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.15444444,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Харддиск WD","depth":10,"bounds":{"left":0.16388889,"top":0.17666666,"width":0.063194446,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.22777778,"top":0.17666666,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.24791667,"top":0.17666666,"width":0.017361112,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", 6TB NAS, 3.5\", 256MB, 5400RPM, WD60EFPX (HDD-SATA...","depth":10,"bounds":{"left":0.26597223,"top":0.17666666,"width":0.2611111,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2211111,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.22222222,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.22222222,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.22222222,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.24444444,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.24444444,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.24444444,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.24444444,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"( 3.5\", 256MB, 5400 RPM, SATA 6Gb/S)…..","depth":10,"bounds":{"left":0.34166667,"top":0.24444444,"width":0.18194444,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.2888889,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.29,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.29,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.29,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Твърддиск, Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.3122222,"width":0.11875,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.28333333,"top":0.3122222,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6TB","depth":10,"bounds":{"left":0.3,"top":0.3122222,"width":0.022222223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Plus","depth":11,"bounds":{"left":0.32291666,"top":0.3122222,"width":0.018055556,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(3.5\", 256MB, 5400 RPM, SATA 6Gb/S) ...","depth":10,"bounds":{"left":0.34166667,"top":0.3122222,"width":0.17708333,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:39","depth":10,"bounds":{"left":0.16388889,"top":0.35666665,"width":0.01875,"height":0.015555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"block","depth":10,"bounds":{"left":0.19027779,"top":0.35777777,"width":0.018055556,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"conf:0.50","depth":10,"bounds":{"left":0.21388888,"top":0.35777777,"width":0.031944446,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[ocr]","depth":10,"bounds":{"left":0.24861111,"top":0.35777777,"width":0.015972223,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital","depth":10,"bounds":{"left":0.16388889,"top":0.38,"width":0.51944447,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Red","depth":11,"bounds":{"left":0.6840278,"top":0.38,"width":0.015972223,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-2078703385584174888
|
8119792561088645234
|
idle
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Sham DXP4800PLUS-B5F8
Inbox (9) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
Red plus
Search
Source
App
any
Role
AXButton…
Date
12
/
04
/
2026
Calendar
From
--
:
--
To
--
:
--
Searches all UI element text from accessibility tree + OCR blocks (51,608 rows) — most thorough
11:38
block
conf:1.00
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:0.50
[ocr]
WD
Red
™
Plus
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)
11:39
block
conf:1.00
[ocr]
WD
Red
Plus
6TB SATA 6Gb (WD60EFPX)|
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5 (WD6OEFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Red
Plus
6TB NAS, 3.5, 256MB, 5400RPM | WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:0.50
[ocr]
Red
Plus
6TB NAS, 3.5 , 256MB, 5400RPM |WD60EFPX (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Хард диск WD
Red
Plus
, 6TB NAS, 3.5 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:38
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Western Digital
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:39
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3
11:38
block
conf:1.00
[ocr]
Производител: Western Digital Модел:
Red
Plus
3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX)
11:39
block
conf:1.00
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Харддиск WD
Red
Plus
, 6TB NAS, 3.5", 256MB, 5400RPM, WD60EFPX (HDD-SATA...
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
( 3.5", 256MB, 5400 RPM, SATA 6Gb/S)…..
11:39
block
conf:0.50
[ocr]
Твърддиск, Western Digital
Red
6TB
Plus
(3.5", 256MB, 5400 RPM, SATA 6Gb/S) ...
11:39
block
conf:0.50
[ocr]
Pazaruvaj.com > Информатика › Хардуер › Вьтрешен хард диск › Вътрешен хард диск Western Digital › Western Digital
Red...
|
NULL
|
|
72858
|
1779
|
3
|
2026-04-23T06:19:48.838839+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776925188838_m2.jpg...
|
PhpStorm
|
faVsco.js – RequestGenerateAskJiminnyReportJobTest faVsco.js – RequestGenerateAskJiminnyReportJobTest.php...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
16
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Routing\UrlGenerator;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportNotGeneratedMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RequestGenerateAskJiminnyReportJobTest extends TestCase
{
private AutomatedReportsService&MockObject $reportService;
private AskJiminnyReportActivityService&MockObject $activityService;
private ProphetClient&MockObject $prophetClient;
private LoggerInterface&MockObject $logger;
private UrlGenerator&MockObject $urlGenerator;
private JobDispatcherInterface&MockObject $jobDispatcher;
protected function setUp(): void
{
$this->reportService = $this->createMock(AutomatedReportsService::class);
$this->activityService = $this->createMock(AskJiminnyReportActivityService::class);
$this->prophetClient = $this->createMock(ProphetClient::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->urlGenerator = $this->createMock(UrlGenerator::class);
$this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);
}
private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob
{
return new RequestGenerateAskJiminnyReportJob($uuid);
}
private function makeActiveReport(
string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,
bool $status = true,
string $teamStatus = Team::STATUS_ACTIVE,
): AutomatedReport&MockObject { // @phpstan-ignore-line
$team = $this->createMock(Team::class);
$team->method('getStatus')->willReturn($teamStatus);
$report = $this->createMock(AutomatedReport::class);
$report->method('getType')->willReturn($type);
$report->method('getStatus')->willReturn($status);
$report->method('getTeam')->willReturn($team);
return $report;
}
public function testUniqueIdReturnsReportUuid(): void
{
$job = $this->makeJob('my-unique-uuid');
$this->assertEquals('my-unique-uuid', $job->uniqueId());
}
public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void
{
$report = $this->makeActiveReport(type: 'exec_summary');
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('info')
->with($this->stringContains('Started'));
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('not an ask_jiminny report'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenReportIsInactive(): void
{
$report = $this->makeActiveReport(status: false);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenTeamIsInactive(): void
{
$report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenCreatorIsNull(): void
{
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('report creator not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenSavedSearchIsNull(): void
{
$creator = $this->createMock(User::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('saved search not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenPromptIsNull(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('ask anything prompt not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleFailsReportWhenNotEnoughActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED
&& $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSuccessfullyRequestsReport(): void
{
Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getUuid')->willReturn('report-uuid');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED
&& isset($data['name'], $data['payload'], $data['requested_at'])));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->reportService->expects($this->once())
->method('getAskJiminnyGenerateReportPayload')
->willReturn(['key' => 'value']);
$this->reportService->expects($this->once())
->method('getReportFileName')
->willReturn('My Report - 7 Apr 2026');
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn(['act-1', 'act-2']);
$this->prophetClient->expects($this->once())
->method('sendRequest')
->willReturn(new \Jiminny\Component\ProphetAi\Dtos\ProphetResponseDto([]));
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
Carbon::setTestNow();
}
public function testHandleCatchesGenericExceptionAndLogsError(): void
{
$this->reportService->expects($this->once())
->method('getReport')
->willThrowException(new \RuntimeException('DB error'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Error'));
$job = $this->makeJob();
$reflection = new \ReflectionClass($job);
$triesProp = $reflection->getProperty('tries');
$triesProp->setAccessible(true);
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willThrowException(new ProphetException('Prophet failed'));
$this->logger->expects($this->once())
->method('error');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCreatesReportResultBeforeActivityFetch(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$callOrder = [];
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturnCallback(function () use ($reportResult, &$callOrder) {
$callOrder[] = 'getOrCreateReportResult';
return $reportResult;
});
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturnCallback(function () use (&$callOrder) {
$callOrder[] = 'getActivityIds';
return [];
});
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);
}
public function testHandlePassesCorrectDataToCreateReportResult(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->with(
automatedReport: $report,
data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT
&& $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)
)
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getCustomName')->willReturn('My AJ Report');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->with($report)
->willReturn([
['email' => '[EMAIL]', 'name' => 'User One', 'timezone' => 'UTC'],
['email' => '[EMAIL]', 'name' => 'User Two', 'timezone' => 'UTC'],
]);
$this->reportService->expects($this->once())
->method('getReportPeriodName')
->with($reportResult)
->willReturn('15 - 30 Jun 2025');
$this->urlGenerator->expects($this->once())
->method('route')
->with('ai.reports.show')
->willReturn('[URL_WITH_CREDENTIALS] QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue, folder
Router, folder
Saml2, folder
SCIM, folder
Seeder, folder
Sentry, folder
Serializer, folder
Settings, folder
Sidekick, folder
Slack, folder
TeamInsights, folder
TimeMemoryMapper, folder
Transcription, folder
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php
AutologDelayedCommand.php
BullhornCommandAbstract.php
BullhornPingCommand.php
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php
SyncHubspotActiveDeals.php
SyncHubspotObjects.php
SyncLead.php
SyncObjects.php
SyncOpportunitiesMissingFieldDataCommand.php
SyncOpportunity.php
SyncProfileMetadata.php
SyncTeamMetadata.php
UpdateOpportunitySpecifications.php
DealInsights, folder
Dev, folder
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.25731382,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20157-AJ-report-not-send-notification, menu","depth":5,"bounds":{"left":0.29587767,"top":0.019952115,"width":0.10139628,"height":0.025538707},"help_text":"Git Branch: JY-20157-AJ-report-not-send-notification","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.7972075,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"bounds":{"left":0.8125,"top":0.019952115,"width":0.10305851,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.51396275,"top":0.22426178,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"16","depth":4,"bounds":{"left":0.52327126,"top":0.22426178,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.5349069,"top":0.22426178,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.54454786,"top":0.22266561,"width":0.00731383,"height":0.018355945},"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.5518617,"top":0.22266561,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Routing\\UrlGenerator;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\SendReportNotGeneratedMailJob;\nuse Jiminny\\Jobs\\JobDispatcherInterface;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass RequestGenerateAskJiminnyReportJobTest extends TestCase\n{\n private AutomatedReportsService&MockObject $reportService;\n private AskJiminnyReportActivityService&MockObject $activityService;\n private ProphetClient&MockObject $prophetClient;\n private LoggerInterface&MockObject $logger;\n private UrlGenerator&MockObject $urlGenerator;\n private JobDispatcherInterface&MockObject $jobDispatcher;\n\n protected function setUp(): void\n {\n $this->reportService = $this->createMock(AutomatedReportsService::class);\n $this->activityService = $this->createMock(AskJiminnyReportActivityService::class);\n $this->prophetClient = $this->createMock(ProphetClient::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n $this->urlGenerator = $this->createMock(UrlGenerator::class);\n $this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);\n }\n\n private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob\n {\n return new RequestGenerateAskJiminnyReportJob($uuid);\n }\n\n private function makeActiveReport(\n string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,\n bool $status = true,\n string $teamStatus = Team::STATUS_ACTIVE,\n ): AutomatedReport&MockObject { // @phpstan-ignore-line\n $team = $this->createMock(Team::class);\n $team->method('getStatus')->willReturn($teamStatus);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn($type);\n $report->method('getStatus')->willReturn($status);\n $report->method('getTeam')->willReturn($team);\n\n return $report;\n }\n\n public function testUniqueIdReturnsReportUuid(): void\n {\n $job = $this->makeJob('my-unique-uuid');\n\n $this->assertEquals('my-unique-uuid', $job->uniqueId());\n }\n\n public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void\n {\n $report = $this->makeActiveReport(type: 'exec_summary');\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with($this->stringContains('Started'));\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('not an ask_jiminny report'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenReportIsInactive(): void\n {\n $report = $this->makeActiveReport(status: false);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenTeamIsInactive(): void\n {\n $report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenCreatorIsNull(): void\n {\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('report creator not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenSavedSearchIsNull(): void\n {\n $creator = $this->createMock(User::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('saved search not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenPromptIsNull(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('ask anything prompt not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleFailsReportWhenNotEnoughActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED\n && $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSuccessfullyRequestsReport(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));\n\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getUuid')->willReturn('report-uuid');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED\n && isset($data['name'], $data['payload'], $data['requested_at'])));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->reportService->expects($this->once())\n ->method('getAskJiminnyGenerateReportPayload')\n ->willReturn(['key' => 'value']);\n\n $this->reportService->expects($this->once())\n ->method('getReportFileName')\n ->willReturn('My Report - 7 Apr 2026');\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn(['act-1', 'act-2']);\n\n $this->prophetClient->expects($this->once())\n ->method('sendRequest')\n ->willReturn(new \\Jiminny\\Component\\ProphetAi\\Dtos\\ProphetResponseDto([]));\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n Carbon::setTestNow();\n }\n\n public function testHandleCatchesGenericExceptionAndLogsError(): void\n {\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willThrowException(new \\RuntimeException('DB error'));\n\n $this->logger->expects($this->once())\n ->method('error')\n ->with($this->stringContains('Error'));\n\n $job = $this->makeJob();\n\n $reflection = new \\ReflectionClass($job);\n $triesProp = $reflection->getProperty('tries');\n $triesProp->setAccessible(true);\n\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willThrowException(new ProphetException('Prophet failed'));\n\n $this->logger->expects($this->once())\n ->method('error');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCreatesReportResultBeforeActivityFetch(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n\n $callOrder = [];\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturnCallback(function () use ($reportResult, &$callOrder) {\n $callOrder[] = 'getOrCreateReportResult';\n\n return $reportResult;\n });\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturnCallback(function () use (&$callOrder) {\n $callOrder[] = 'getActivityIds';\n\n return [];\n });\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);\n }\n\n public function testHandlePassesCorrectDataToCreateReportResult(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->with(\n automatedReport: $report,\n data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT\n && $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)\n )\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getCustomName')->willReturn('My AJ Report');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->with($report)\n ->willReturn([\n ['email' => 'user1@example.com', 'name' => 'User One', 'timezone' => 'UTC'],\n ['email' => 'user2@example.com', 'name' => 'User Two', 'timezone' => 'UTC'],\n ]);\n\n $this->reportService->expects($this->once())\n ->method('getReportPeriodName')\n ->with($reportResult)\n ->willReturn('15 - 30 Jun 2025');\n\n $this->urlGenerator->expects($this->once())\n ->method('route')\n ->with('ai.reports.show')\n ->willReturn('https://example.com/ai-reports');\n\n $dispatchedJobs = [];\n $this->jobDispatcher->expects($this->exactly(2))\n ->method('dispatch')\n ->willReturnCallback(function ($dispatchedJob) use (&$dispatchedJobs) {\n $dispatchedJobs[] = $dispatchedJob;\n });\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertCount(2, $dispatchedJobs);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[0]);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[1]);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Routing\\UrlGenerator;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\SendReportNotGeneratedMailJob;\nuse Jiminny\\Jobs\\JobDispatcherInterface;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass RequestGenerateAskJiminnyReportJobTest extends TestCase\n{\n private AutomatedReportsService&MockObject $reportService;\n private AskJiminnyReportActivityService&MockObject $activityService;\n private ProphetClient&MockObject $prophetClient;\n private LoggerInterface&MockObject $logger;\n private UrlGenerator&MockObject $urlGenerator;\n private JobDispatcherInterface&MockObject $jobDispatcher;\n\n protected function setUp(): void\n {\n $this->reportService = $this->createMock(AutomatedReportsService::class);\n $this->activityService = $this->createMock(AskJiminnyReportActivityService::class);\n $this->prophetClient = $this->createMock(ProphetClient::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n $this->urlGenerator = $this->createMock(UrlGenerator::class);\n $this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);\n }\n\n private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob\n {\n return new RequestGenerateAskJiminnyReportJob($uuid);\n }\n\n private function makeActiveReport(\n string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,\n bool $status = true,\n string $teamStatus = Team::STATUS_ACTIVE,\n ): AutomatedReport&MockObject { // @phpstan-ignore-line\n $team = $this->createMock(Team::class);\n $team->method('getStatus')->willReturn($teamStatus);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn($type);\n $report->method('getStatus')->willReturn($status);\n $report->method('getTeam')->willReturn($team);\n\n return $report;\n }\n\n public function testUniqueIdReturnsReportUuid(): void\n {\n $job = $this->makeJob('my-unique-uuid');\n\n $this->assertEquals('my-unique-uuid', $job->uniqueId());\n }\n\n public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void\n {\n $report = $this->makeActiveReport(type: 'exec_summary');\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with($this->stringContains('Started'));\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('not an ask_jiminny report'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenReportIsInactive(): void\n {\n $report = $this->makeActiveReport(status: false);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenTeamIsInactive(): void\n {\n $report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenCreatorIsNull(): void\n {\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('report creator not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenSavedSearchIsNull(): void\n {\n $creator = $this->createMock(User::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('saved search not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenPromptIsNull(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('ask anything prompt not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleFailsReportWhenNotEnoughActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED\n && $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSuccessfullyRequestsReport(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));\n\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getUuid')->willReturn('report-uuid');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED\n && isset($data['name'], $data['payload'], $data['requested_at'])));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->reportService->expects($this->once())\n ->method('getAskJiminnyGenerateReportPayload')\n ->willReturn(['key' => 'value']);\n\n $this->reportService->expects($this->once())\n ->method('getReportFileName')\n ->willReturn('My Report - 7 Apr 2026');\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn(['act-1', 'act-2']);\n\n $this->prophetClient->expects($this->once())\n ->method('sendRequest')\n ->willReturn(new \\Jiminny\\Component\\ProphetAi\\Dtos\\ProphetResponseDto([]));\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n Carbon::setTestNow();\n }\n\n public function testHandleCatchesGenericExceptionAndLogsError(): void\n {\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willThrowException(new \\RuntimeException('DB error'));\n\n $this->logger->expects($this->once())\n ->method('error')\n ->with($this->stringContains('Error'));\n\n $job = $this->makeJob();\n\n $reflection = new \\ReflectionClass($job);\n $triesProp = $reflection->getProperty('tries');\n $triesProp->setAccessible(true);\n\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willThrowException(new ProphetException('Prophet failed'));\n\n $this->logger->expects($this->once())\n ->method('error');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCreatesReportResultBeforeActivityFetch(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n\n $callOrder = [];\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturnCallback(function () use ($reportResult, &$callOrder) {\n $callOrder[] = 'getOrCreateReportResult';\n\n return $reportResult;\n });\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturnCallback(function () use (&$callOrder) {\n $callOrder[] = 'getActivityIds';\n\n return [];\n });\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);\n }\n\n public function testHandlePassesCorrectDataToCreateReportResult(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->with(\n automatedReport: $report,\n data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT\n && $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)\n )\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getCustomName')->willReturn('My AJ Report');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->with($report)\n ->willReturn([\n ['email' => 'user1@example.com', 'name' => 'User One', 'timezone' => 'UTC'],\n ['email' => 'user2@example.com', 'name' => 'User Two', 'timezone' => 'UTC'],\n ]);\n\n $this->reportService->expects($this->once())\n ->method('getReportPeriodName')\n ->with($reportResult)\n ->willReturn('15 - 30 Jun 2025');\n\n $this->urlGenerator->expects($this->once())\n ->method('route')\n ->with('ai.reports.show')\n ->willReturn('https://example.com/ai-reports');\n\n $dispatchedJobs = [];\n $this->jobDispatcher->expects($this->exactly(2))\n ->method('dispatch')\n ->willReturnCallback(function ($dispatchedJob) use (&$dispatchedJobs) {\n $dispatchedJobs[] = $dispatchedJob;\n });\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertCount(2, $dispatchedJobs);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[0]);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[1]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"36","depth":4,"bounds":{"left":0.69980055,"top":0.15003991,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7117686,"top":0.14844373,"width":0.00731383,"height":0.018355945},"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.7190825,"top":0.14844373,"width":0.006981383,"height":0.018355945},"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\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.24335106,"top":0.047885075,"width":0.024268618,"height":0.024740623},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Dtos","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Events","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AWS","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Cache","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Country","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Database","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Datadog","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DateTime","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregator.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregatorInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseActivities.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatasourceInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivity.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivityInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Comments","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Forecast","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Jobs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"QueryBuilder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Services","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ClosingPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreatedPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Criteria.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaNormalizer.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealContactService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsCriteriaBuilder.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepositoryInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsServiceRepositories.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PerformanceMonitor.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodOptionDecoratorInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodService.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskTypes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisk.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GroupDealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encoding, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encryption, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Locks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Math, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Uploader, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Utility, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Uuid, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Waveform, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Workflow, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Configuration, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php","depth":11,"role_description":"text"}]...
|
-5434174588617013835
|
8118578209167329727
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
16
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Routing\UrlGenerator;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportNotGeneratedMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RequestGenerateAskJiminnyReportJobTest extends TestCase
{
private AutomatedReportsService&MockObject $reportService;
private AskJiminnyReportActivityService&MockObject $activityService;
private ProphetClient&MockObject $prophetClient;
private LoggerInterface&MockObject $logger;
private UrlGenerator&MockObject $urlGenerator;
private JobDispatcherInterface&MockObject $jobDispatcher;
protected function setUp(): void
{
$this->reportService = $this->createMock(AutomatedReportsService::class);
$this->activityService = $this->createMock(AskJiminnyReportActivityService::class);
$this->prophetClient = $this->createMock(ProphetClient::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->urlGenerator = $this->createMock(UrlGenerator::class);
$this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);
}
private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob
{
return new RequestGenerateAskJiminnyReportJob($uuid);
}
private function makeActiveReport(
string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,
bool $status = true,
string $teamStatus = Team::STATUS_ACTIVE,
): AutomatedReport&MockObject { // @phpstan-ignore-line
$team = $this->createMock(Team::class);
$team->method('getStatus')->willReturn($teamStatus);
$report = $this->createMock(AutomatedReport::class);
$report->method('getType')->willReturn($type);
$report->method('getStatus')->willReturn($status);
$report->method('getTeam')->willReturn($team);
return $report;
}
public function testUniqueIdReturnsReportUuid(): void
{
$job = $this->makeJob('my-unique-uuid');
$this->assertEquals('my-unique-uuid', $job->uniqueId());
}
public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void
{
$report = $this->makeActiveReport(type: 'exec_summary');
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('info')
->with($this->stringContains('Started'));
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('not an ask_jiminny report'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenReportIsInactive(): void
{
$report = $this->makeActiveReport(status: false);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenTeamIsInactive(): void
{
$report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenCreatorIsNull(): void
{
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('report creator not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenSavedSearchIsNull(): void
{
$creator = $this->createMock(User::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('saved search not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenPromptIsNull(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('ask anything prompt not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleFailsReportWhenNotEnoughActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED
&& $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSuccessfullyRequestsReport(): void
{
Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getUuid')->willReturn('report-uuid');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED
&& isset($data['name'], $data['payload'], $data['requested_at'])));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->reportService->expects($this->once())
->method('getAskJiminnyGenerateReportPayload')
->willReturn(['key' => 'value']);
$this->reportService->expects($this->once())
->method('getReportFileName')
->willReturn('My Report - 7 Apr 2026');
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn(['act-1', 'act-2']);
$this->prophetClient->expects($this->once())
->method('sendRequest')
->willReturn(new \Jiminny\Component\ProphetAi\Dtos\ProphetResponseDto([]));
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
Carbon::setTestNow();
}
public function testHandleCatchesGenericExceptionAndLogsError(): void
{
$this->reportService->expects($this->once())
->method('getReport')
->willThrowException(new \RuntimeException('DB error'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Error'));
$job = $this->makeJob();
$reflection = new \ReflectionClass($job);
$triesProp = $reflection->getProperty('tries');
$triesProp->setAccessible(true);
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willThrowException(new ProphetException('Prophet failed'));
$this->logger->expects($this->once())
->method('error');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCreatesReportResultBeforeActivityFetch(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$callOrder = [];
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturnCallback(function () use ($reportResult, &$callOrder) {
$callOrder[] = 'getOrCreateReportResult';
return $reportResult;
});
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturnCallback(function () use (&$callOrder) {
$callOrder[] = 'getActivityIds';
return [];
});
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);
}
public function testHandlePassesCorrectDataToCreateReportResult(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->with(
automatedReport: $report,
data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT
&& $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)
)
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getCustomName')->willReturn('My AJ Report');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->with($report)
->willReturn([
['email' => '[EMAIL]', 'name' => 'User One', 'timezone' => 'UTC'],
['email' => '[EMAIL]', 'name' => 'User Two', 'timezone' => 'UTC'],
]);
$this->reportService->expects($this->once())
->method('getReportPeriodName')
->with($reportResult)
->willReturn('15 - 30 Jun 2025');
$this->urlGenerator->expects($this->once())
->method('route')
->with('ai.reports.show')
->willReturn('[URL_WITH_CREDENTIALS] QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue, folder
Router, folder
Saml2, folder
SCIM, folder
Seeder, folder
Sentry, folder
Serializer, folder
Settings, folder
Sidekick, folder
Slack, folder
TeamInsights, folder
TimeMemoryMapper, folder
Transcription, folder
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php
AutologDelayedCommand.php
BullhornCommandAbstract.php
BullhornPingCommand.php
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php
SyncHubspotActiveDeals.php
SyncHubspotObjects.php
SyncLead.php
SyncObjects.php
SyncOpportunitiesMissingFieldDataCommand.php
SyncOpportunity.php
SyncProfileMetadata.php
SyncTeamMetadata.php
UpdateOpportunitySpecifications.php
DealInsights, folder
Dev, folder
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php...
|
72856
|
|
72857
|
1778
|
2
|
2026-04-23T06:19:48.036436+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776925188036_m1.jpg...
|
PhpStorm
|
faVsco.js – RequestGenerateAskJiminnyReportJobTest faVsco.js – RequestGenerateAskJiminnyReportJobTest.php...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
16
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Routing\UrlGenerator;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportNotGeneratedMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RequestGenerateAskJiminnyReportJobTest extends TestCase
{
private AutomatedReportsService&MockObject $reportService;
private AskJiminnyReportActivityService&MockObject $activityService;
private ProphetClient&MockObject $prophetClient;
private LoggerInterface&MockObject $logger;
private UrlGenerator&MockObject $urlGenerator;
private JobDispatcherInterface&MockObject $jobDispatcher;
protected function setUp(): void
{
$this->reportService = $this->createMock(AutomatedReportsService::class);
$this->activityService = $this->createMock(AskJiminnyReportActivityService::class);
$this->prophetClient = $this->createMock(ProphetClient::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->urlGenerator = $this->createMock(UrlGenerator::class);
$this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);
}
private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob
{
return new RequestGenerateAskJiminnyReportJob($uuid);
}
private function makeActiveReport(
string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,
bool $status = true,
string $teamStatus = Team::STATUS_ACTIVE,
): AutomatedReport&MockObject { // @phpstan-ignore-line
$team = $this->createMock(Team::class);
$team->method('getStatus')->willReturn($teamStatus);
$report = $this->createMock(AutomatedReport::class);
$report->method('getType')->willReturn($type);
$report->method('getStatus')->willReturn($status);
$report->method('getTeam')->willReturn($team);
return $report;
}
public function testUniqueIdReturnsReportUuid(): void
{
$job = $this->makeJob('my-unique-uuid');
$this->assertEquals('my-unique-uuid', $job->uniqueId());
}
public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void
{
$report = $this->makeActiveReport(type: 'exec_summary');
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('info')
->with($this->stringContains('Started'));
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('not an ask_jiminny report'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenReportIsInactive(): void
{
$report = $this->makeActiveReport(status: false);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenTeamIsInactive(): void
{
$report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenCreatorIsNull(): void
{
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('report creator not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenSavedSearchIsNull(): void
{
$creator = $this->createMock(User::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('saved search not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenPromptIsNull(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('ask anything prompt not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleFailsReportWhenNotEnoughActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED
&& $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSuccessfullyRequestsReport(): void
{
Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getUuid')->willReturn('report-uuid');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED
&& isset($data['name'], $data['payload'], $data['requested_at'])));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->reportService->expects($this->once())
->method('getAskJiminnyGenerateReportPayload')
->willReturn(['key' => 'value']);
$this->reportService->expects($this->once())
->method('getReportFileName')
->willReturn('My Report - 7 Apr 2026');
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn(['act-1', 'act-2']);
$this->prophetClient->expects($this->once())
->method('sendRequest')
->willReturn(new \Jiminny\Component\ProphetAi\Dtos\ProphetResponseDto([]));
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
Carbon::setTestNow();
}
public function testHandleCatchesGenericExceptionAndLogsError(): void
{
$this->reportService->expects($this->once())
->method('getReport')
->willThrowException(new \RuntimeException('DB error'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Error'));
$job = $this->makeJob();
$reflection = new \ReflectionClass($job);
$triesProp = $reflection->getProperty('tries');
$triesProp->setAccessible(true);
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willThrowException(new ProphetException('Prophet failed'));
$this->logger->expects($this->once())
->method('error');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCreatesReportResultBeforeActivityFetch(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$callOrder = [];
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturnCallback(function () use ($reportResult, &$callOrder) {
$callOrder[] = 'getOrCreateReportResult';
return $reportResult;
});
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturnCallback(function () use (&$callOrder) {
$callOrder[] = 'getActivityIds';
return [];
});
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);
}
public function testHandlePassesCorrectDataToCreateReportResult(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->with(
automatedReport: $report,
data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT
&& $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)
)
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getCustomName')->willReturn('My AJ Report');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->with($report)
->willReturn([
['email' => '[EMAIL]', 'name' => 'User One', 'timezone' => 'UTC'],
['email' => '[EMAIL]', 'name' => 'User Two', 'timezone' => 'UTC'],
]);
$this->reportService->expects($this->once())
->method('getReportPeriodName')
->with($reportResult)
->willReturn('15 - 30 Jun 2025');
$this->urlGenerator->expects($this->once())
->method('route')
->with('ai.reports.show')
->willReturn('[URL_WITH_CREDENTIALS] QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20157-AJ-report-not-send-notification, menu","depth":5,"help_text":"Git Branch: JY-20157-AJ-report-not-send-notification","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"16","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Routing\\UrlGenerator;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\SendReportNotGeneratedMailJob;\nuse Jiminny\\Jobs\\JobDispatcherInterface;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass RequestGenerateAskJiminnyReportJobTest extends TestCase\n{\n private AutomatedReportsService&MockObject $reportService;\n private AskJiminnyReportActivityService&MockObject $activityService;\n private ProphetClient&MockObject $prophetClient;\n private LoggerInterface&MockObject $logger;\n private UrlGenerator&MockObject $urlGenerator;\n private JobDispatcherInterface&MockObject $jobDispatcher;\n\n protected function setUp(): void\n {\n $this->reportService = $this->createMock(AutomatedReportsService::class);\n $this->activityService = $this->createMock(AskJiminnyReportActivityService::class);\n $this->prophetClient = $this->createMock(ProphetClient::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n $this->urlGenerator = $this->createMock(UrlGenerator::class);\n $this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);\n }\n\n private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob\n {\n return new RequestGenerateAskJiminnyReportJob($uuid);\n }\n\n private function makeActiveReport(\n string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,\n bool $status = true,\n string $teamStatus = Team::STATUS_ACTIVE,\n ): AutomatedReport&MockObject { // @phpstan-ignore-line\n $team = $this->createMock(Team::class);\n $team->method('getStatus')->willReturn($teamStatus);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn($type);\n $report->method('getStatus')->willReturn($status);\n $report->method('getTeam')->willReturn($team);\n\n return $report;\n }\n\n public function testUniqueIdReturnsReportUuid(): void\n {\n $job = $this->makeJob('my-unique-uuid');\n\n $this->assertEquals('my-unique-uuid', $job->uniqueId());\n }\n\n public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void\n {\n $report = $this->makeActiveReport(type: 'exec_summary');\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with($this->stringContains('Started'));\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('not an ask_jiminny report'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenReportIsInactive(): void\n {\n $report = $this->makeActiveReport(status: false);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenTeamIsInactive(): void\n {\n $report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenCreatorIsNull(): void\n {\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('report creator not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenSavedSearchIsNull(): void\n {\n $creator = $this->createMock(User::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('saved search not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenPromptIsNull(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('ask anything prompt not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleFailsReportWhenNotEnoughActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED\n && $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSuccessfullyRequestsReport(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));\n\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getUuid')->willReturn('report-uuid');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED\n && isset($data['name'], $data['payload'], $data['requested_at'])));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->reportService->expects($this->once())\n ->method('getAskJiminnyGenerateReportPayload')\n ->willReturn(['key' => 'value']);\n\n $this->reportService->expects($this->once())\n ->method('getReportFileName')\n ->willReturn('My Report - 7 Apr 2026');\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn(['act-1', 'act-2']);\n\n $this->prophetClient->expects($this->once())\n ->method('sendRequest')\n ->willReturn(new \\Jiminny\\Component\\ProphetAi\\Dtos\\ProphetResponseDto([]));\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n Carbon::setTestNow();\n }\n\n public function testHandleCatchesGenericExceptionAndLogsError(): void\n {\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willThrowException(new \\RuntimeException('DB error'));\n\n $this->logger->expects($this->once())\n ->method('error')\n ->with($this->stringContains('Error'));\n\n $job = $this->makeJob();\n\n $reflection = new \\ReflectionClass($job);\n $triesProp = $reflection->getProperty('tries');\n $triesProp->setAccessible(true);\n\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willThrowException(new ProphetException('Prophet failed'));\n\n $this->logger->expects($this->once())\n ->method('error');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCreatesReportResultBeforeActivityFetch(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n\n $callOrder = [];\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturnCallback(function () use ($reportResult, &$callOrder) {\n $callOrder[] = 'getOrCreateReportResult';\n\n return $reportResult;\n });\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturnCallback(function () use (&$callOrder) {\n $callOrder[] = 'getActivityIds';\n\n return [];\n });\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);\n }\n\n public function testHandlePassesCorrectDataToCreateReportResult(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->with(\n automatedReport: $report,\n data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT\n && $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)\n )\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getCustomName')->willReturn('My AJ Report');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->with($report)\n ->willReturn([\n ['email' => 'user1@example.com', 'name' => 'User One', 'timezone' => 'UTC'],\n ['email' => 'user2@example.com', 'name' => 'User Two', 'timezone' => 'UTC'],\n ]);\n\n $this->reportService->expects($this->once())\n ->method('getReportPeriodName')\n ->with($reportResult)\n ->willReturn('15 - 30 Jun 2025');\n\n $this->urlGenerator->expects($this->once())\n ->method('route')\n ->with('ai.reports.show')\n ->willReturn('https://example.com/ai-reports');\n\n $dispatchedJobs = [];\n $this->jobDispatcher->expects($this->exactly(2))\n ->method('dispatch')\n ->willReturnCallback(function ($dispatchedJob) use (&$dispatchedJobs) {\n $dispatchedJobs[] = $dispatchedJob;\n });\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertCount(2, $dispatchedJobs);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[0]);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[1]);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Routing\\UrlGenerator;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\SendReportNotGeneratedMailJob;\nuse Jiminny\\Jobs\\JobDispatcherInterface;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass RequestGenerateAskJiminnyReportJobTest extends TestCase\n{\n private AutomatedReportsService&MockObject $reportService;\n private AskJiminnyReportActivityService&MockObject $activityService;\n private ProphetClient&MockObject $prophetClient;\n private LoggerInterface&MockObject $logger;\n private UrlGenerator&MockObject $urlGenerator;\n private JobDispatcherInterface&MockObject $jobDispatcher;\n\n protected function setUp(): void\n {\n $this->reportService = $this->createMock(AutomatedReportsService::class);\n $this->activityService = $this->createMock(AskJiminnyReportActivityService::class);\n $this->prophetClient = $this->createMock(ProphetClient::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n $this->urlGenerator = $this->createMock(UrlGenerator::class);\n $this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);\n }\n\n private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob\n {\n return new RequestGenerateAskJiminnyReportJob($uuid);\n }\n\n private function makeActiveReport(\n string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,\n bool $status = true,\n string $teamStatus = Team::STATUS_ACTIVE,\n ): AutomatedReport&MockObject { // @phpstan-ignore-line\n $team = $this->createMock(Team::class);\n $team->method('getStatus')->willReturn($teamStatus);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn($type);\n $report->method('getStatus')->willReturn($status);\n $report->method('getTeam')->willReturn($team);\n\n return $report;\n }\n\n public function testUniqueIdReturnsReportUuid(): void\n {\n $job = $this->makeJob('my-unique-uuid');\n\n $this->assertEquals('my-unique-uuid', $job->uniqueId());\n }\n\n public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void\n {\n $report = $this->makeActiveReport(type: 'exec_summary');\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with($this->stringContains('Started'));\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('not an ask_jiminny report'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenReportIsInactive(): void\n {\n $report = $this->makeActiveReport(status: false);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenTeamIsInactive(): void\n {\n $report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->exactly(2))\n ->method('info');\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenCreatorIsNull(): void\n {\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('report creator not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenSavedSearchIsNull(): void\n {\n $creator = $this->createMock(User::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('saved search not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSkipsWhenPromptIsNull(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn(null);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->logger->expects($this->once())\n ->method('warning')\n ->with($this->stringContains('ask anything prompt not found'));\n\n $this->reportService->expects($this->never())->method('getOrCreateReportResult');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleFailsReportWhenNotEnoughActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED\n && $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleSuccessfullyRequestsReport(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));\n\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getUuid')->willReturn('report-uuid');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED\n && isset($data['name'], $data['payload'], $data['requested_at'])));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->reportService->expects($this->once())\n ->method('getAskJiminnyGenerateReportPayload')\n ->willReturn(['key' => 'value']);\n\n $this->reportService->expects($this->once())\n ->method('getReportFileName')\n ->willReturn('My Report - 7 Apr 2026');\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn(['act-1', 'act-2']);\n\n $this->prophetClient->expects($this->once())\n ->method('sendRequest')\n ->willReturn(new \\Jiminny\\Component\\ProphetAi\\Dtos\\ProphetResponseDto([]));\n\n $this->logger->expects($this->exactly(4))\n ->method('info');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n Carbon::setTestNow();\n }\n\n public function testHandleCatchesGenericExceptionAndLogsError(): void\n {\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willThrowException(new \\RuntimeException('DB error'));\n\n $this->logger->expects($this->once())\n ->method('error')\n ->with($this->stringContains('Error'));\n\n $job = $this->makeJob();\n\n $reflection = new \\ReflectionClass($job);\n $triesProp = $reflection->getProperty('tries');\n $triesProp->setAccessible(true);\n\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->expects($this->once())\n ->method('update')\n ->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willThrowException(new ProphetException('Prophet failed'));\n\n $this->logger->expects($this->once())\n ->method('error');\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleCreatesReportResultBeforeActivityFetch(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n\n $callOrder = [];\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturnCallback(function () use ($reportResult, &$callOrder) {\n $callOrder[] = 'getOrCreateReportResult';\n\n return $reportResult;\n });\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturnCallback(function () use (&$callOrder) {\n $callOrder[] = 'getActivityIds';\n\n return [];\n });\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);\n }\n\n public function testHandlePassesCorrectDataToCreateReportResult(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->with(\n automatedReport: $report,\n data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT\n && $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)\n )\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->willReturn([]);\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n }\n\n public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void\n {\n $creator = $this->createMock(User::class);\n $savedSearch = $this->createMock(\\Jiminny\\Models\\Activity\\Search::class);\n $prompt = $this->createMock(\\Jiminny\\Models\\AskAnything\\AskAnythingPrompt::class);\n\n $report = $this->makeActiveReport();\n $report->method('getCreator')->willReturn($creator);\n $report->method('getSavedSearch')->willReturn($savedSearch);\n $report->method('getAskAnythingPrompt')->willReturn($prompt);\n $report->method('getCustomName')->willReturn('My AJ Report');\n\n $reportResult = $this->createMock(AutomatedReportResult::class);\n $reportResult->method('getUuid')->willReturn('result-uuid');\n $reportResult->method('update')->willReturn(true);\n\n $this->reportService->expects($this->once())\n ->method('getReport')\n ->willReturn($report);\n\n $this->reportService->expects($this->once())\n ->method('getOrCreateReportResult')\n ->willReturn($reportResult);\n\n $this->activityService->expects($this->once())\n ->method('getActivityIdsForSavedSearch')\n ->willReturn([]);\n\n $this->reportService->expects($this->once())\n ->method('getValidRecipientUsers')\n ->with($report)\n ->willReturn([\n ['email' => 'user1@example.com', 'name' => 'User One', 'timezone' => 'UTC'],\n ['email' => 'user2@example.com', 'name' => 'User Two', 'timezone' => 'UTC'],\n ]);\n\n $this->reportService->expects($this->once())\n ->method('getReportPeriodName')\n ->with($reportResult)\n ->willReturn('15 - 30 Jun 2025');\n\n $this->urlGenerator->expects($this->once())\n ->method('route')\n ->with('ai.reports.show')\n ->willReturn('https://example.com/ai-reports');\n\n $dispatchedJobs = [];\n $this->jobDispatcher->expects($this->exactly(2))\n ->method('dispatch')\n ->willReturnCallback(function ($dispatchedJob) use (&$dispatchedJobs) {\n $dispatchedJobs[] = $dispatchedJob;\n });\n\n $job = $this->makeJob();\n $job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);\n\n $this->assertCount(2, $dispatchedJobs);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[0]);\n $this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[1]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"36","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Dtos","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Events","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AWS","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Cache","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Country","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Database","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Datadog","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DateTime","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregator.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregatorInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseActivities.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatasourceInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivity.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivityInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Comments","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Forecast","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Jobs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"QueryBuilder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Services","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ClosingPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreatedPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Criteria.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaNormalizer.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealContactService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsCriteriaBuilder.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepositoryInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsServiceRepositories.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PerformanceMonitor.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodOptionDecoratorInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodService.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskTypes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisk.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GroupDealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encoding, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encryption, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection, folder","depth":9,"role_description":"text"}]...
|
-738382506339040260
|
8118523216406071743
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
16
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Routing\UrlGenerator;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportNotGeneratedMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RequestGenerateAskJiminnyReportJobTest extends TestCase
{
private AutomatedReportsService&MockObject $reportService;
private AskJiminnyReportActivityService&MockObject $activityService;
private ProphetClient&MockObject $prophetClient;
private LoggerInterface&MockObject $logger;
private UrlGenerator&MockObject $urlGenerator;
private JobDispatcherInterface&MockObject $jobDispatcher;
protected function setUp(): void
{
$this->reportService = $this->createMock(AutomatedReportsService::class);
$this->activityService = $this->createMock(AskJiminnyReportActivityService::class);
$this->prophetClient = $this->createMock(ProphetClient::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->urlGenerator = $this->createMock(UrlGenerator::class);
$this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);
}
private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob
{
return new RequestGenerateAskJiminnyReportJob($uuid);
}
private function makeActiveReport(
string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,
bool $status = true,
string $teamStatus = Team::STATUS_ACTIVE,
): AutomatedReport&MockObject { // @phpstan-ignore-line
$team = $this->createMock(Team::class);
$team->method('getStatus')->willReturn($teamStatus);
$report = $this->createMock(AutomatedReport::class);
$report->method('getType')->willReturn($type);
$report->method('getStatus')->willReturn($status);
$report->method('getTeam')->willReturn($team);
return $report;
}
public function testUniqueIdReturnsReportUuid(): void
{
$job = $this->makeJob('my-unique-uuid');
$this->assertEquals('my-unique-uuid', $job->uniqueId());
}
public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void
{
$report = $this->makeActiveReport(type: 'exec_summary');
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('info')
->with($this->stringContains('Started'));
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('not an ask_jiminny report'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenReportIsInactive(): void
{
$report = $this->makeActiveReport(status: false);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenTeamIsInactive(): void
{
$report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenCreatorIsNull(): void
{
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('report creator not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenSavedSearchIsNull(): void
{
$creator = $this->createMock(User::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('saved search not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenPromptIsNull(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('ask anything prompt not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleFailsReportWhenNotEnoughActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED
&& $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSuccessfullyRequestsReport(): void
{
Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getUuid')->willReturn('report-uuid');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED
&& isset($data['name'], $data['payload'], $data['requested_at'])));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->reportService->expects($this->once())
->method('getAskJiminnyGenerateReportPayload')
->willReturn(['key' => 'value']);
$this->reportService->expects($this->once())
->method('getReportFileName')
->willReturn('My Report - 7 Apr 2026');
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn(['act-1', 'act-2']);
$this->prophetClient->expects($this->once())
->method('sendRequest')
->willReturn(new \Jiminny\Component\ProphetAi\Dtos\ProphetResponseDto([]));
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
Carbon::setTestNow();
}
public function testHandleCatchesGenericExceptionAndLogsError(): void
{
$this->reportService->expects($this->once())
->method('getReport')
->willThrowException(new \RuntimeException('DB error'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Error'));
$job = $this->makeJob();
$reflection = new \ReflectionClass($job);
$triesProp = $reflection->getProperty('tries');
$triesProp->setAccessible(true);
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willThrowException(new ProphetException('Prophet failed'));
$this->logger->expects($this->once())
->method('error');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCreatesReportResultBeforeActivityFetch(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$callOrder = [];
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturnCallback(function () use ($reportResult, &$callOrder) {
$callOrder[] = 'getOrCreateReportResult';
return $reportResult;
});
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturnCallback(function () use (&$callOrder) {
$callOrder[] = 'getActivityIds';
return [];
});
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);
}
public function testHandlePassesCorrectDataToCreateReportResult(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->with(
automatedReport: $report,
data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT
&& $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)
)
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getCustomName')->willReturn('My AJ Report');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->with($report)
->willReturn([
['email' => '[EMAIL]', 'name' => 'User One', 'timezone' => 'UTC'],
['email' => '[EMAIL]', 'name' => 'User Two', 'timezone' => 'UTC'],
]);
$this->reportService->expects($this->once())
->method('getReportPeriodName')
->with($reportResult)
->willReturn('15 - 30 Jun 2025');
$this->urlGenerator->expects($this->once())
->method('route')
->with('ai.reports.show')
->willReturn('[URL_WITH_CREDENTIALS] QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder...
|
NULL
|
|
7403
|
138
|
8
|
2026-04-13T15:48:08.455065+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776095288455_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
http://100.73.206.126:8766
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Inbox (10) - [EMAIL] - Gmail
Sha DXP4800PLUS-B5F8
Inbox (10) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Google Account: Lukáš Koválik ([EMAIL])
Main menu
New chat
Gemini
Temporary chat
PLUS
PLUS
Conversation with Gemini
Conversation with Gemini
Hi Lukáš
Where should we start?
Where should we start?
🖼️ Create image, button, tap to use tool
🖼️ Create image
🎸 Create music, button, tap to use tool
🎸 Create music
Boost my day, button, tap to use tool
Boost my day
Help me learn, button, tap to use tool
Help me learn
Write anything, button, tap to use tool
Write anything
Create a video, button, tap to use tool
Create a video
Dismiss
Try video templates instead of starting from scratch
Try it
Try it
Ask Gemini
Ask Gemini
Open upload file menu
Tools
Open mode picker
Pro
Microphone
Summarize page
Summarize page
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
TOTAL SPAN
12.6h
11:35 → 00:10
ACTIVE
4.6h
BREAKS
3 breaks · 8.0h
SESSIONS
4
63m
87m
114m
117m
340m
FRAMES
2455
APPS
10
UI EVENTS
3628
AUDIO
0
ACTIVE PERIOD
11:35 → 00:10
TIME PER APP
— CLICK TO FILTER RIGHT PANEL
Boosteroid
43.4m
Firefox
9.7m
iTerm2
5.7m
Claude
5.1m
System Settings
1.9m
Alfred
0.4m
Control Centre
0.2m
Code
0.1m
Activity Monitor
0.1m
Preview
0m
Websites
Windows
UI Events
screenpi.pe/onboarding
0.6m
dennikn.sk
0.3m
screenpi.pe/ideas
0.2m
nas.lakylak.xyz/desktop/#/
0.2m
screenpi.pe/why
0.2m
www.reddit.com/r/software/comments/1fjfwg0/comment/lnr7nqd/
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909
0.1m
app.dev.jiminny.com
0.1m
addons.mozilla.org/en-US/firefox/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=find-more-link-bottom
0.1m
screenpi.pe/resources
0.1m
screenpi.pe
0.1m
mail.google.com/mail/u/0/#inbox
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543
0.1m
http://localhost:3030
0.1m
addons.mozilla.org/en-US/firefox/addon/proxy-switcher-and-manager/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search
0.1m
www.rtings.com/projector/reviews/dangbei/atom-laser-projector
0m
www.reddit.com/r/software/comments/1fjfwg0/screenpipe_open_source_247_screen_audio_capture/
0m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37
0m
dennikn.sk/tema/iran/
0m
dennikn.sk/5268962/sledujte-madarske-volby-ako-experti-toto-su-najdolezitejsie-veci-ktore-treba-vediet/
0m
addons.mozilla.org/en-US/firefox/search/?q=profile
0m...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (10) - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Shameless • HBO Max","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shameless • HBO Max","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Settings","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"firefox sidebar - Google Search","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"firefox sidebar - Google Search","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"How to use AI-enhanced tab groups | Firefox Help","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"How to use AI-enhanced tab groups | Firefox Help","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons Manager","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons Manager","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"dennikn.sk/","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dennikn.sk/","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Vimium Options","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vimium Options","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Machines - Tailscale","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Machines - Tailscale","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Browser Extension Getting Started | Bitwarden","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Browser Extension Getting Started | Bitwarden","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Extensions – Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Extensions – Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dangbei Atom Review - RTINGS.com","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dangbei Atom Review - RTINGS.com","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Add-ons for Firefox (en-US)","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add-ons for Firefox (en-US)","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to Firefox","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Firefox","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google Account: Lukáš Koválik (kovaliklukas@gmail.com)","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Temporary chat","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"PLUS","depth":11,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"PLUS","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hi Lukáš","depth":23,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Where should we start?","depth":22,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Where should we start?","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"🖼️ Create image, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"🖼️ Create image","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"🎸 Create music, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"🎸 Create music","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Boost my day, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Boost my day","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help me learn, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help me learn","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Write anything, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Write anything","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create a video, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create a video","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss","depth":19,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Try video templates instead of starting from scratch","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Try it","depth":19,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Try it","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextArea","text":"Ask Gemini","depth":20,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open upload file menu","depth":20,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tools","depth":18,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open mode picker","depth":20,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pro","depth":23,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Microphone","depth":19,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Summarize page","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Screenpipe","depth":7,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"TOTAL SPAN","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12.6h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:35 → 00:10","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ACTIVE","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.6h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BREAKS","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3 breaks · 8.0h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SESSIONS","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"63m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"87m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"114m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"117m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"340m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FRAMES","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2455","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"APPS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UI EVENTS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3628","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AUDIO","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ACTIVE PERIOD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:35 → 00:10","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TIME PER APP","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— CLICK TO FILTER RIGHT PANEL","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Boosteroid","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"43.4m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Firefox","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9.7m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iTerm2","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5.7m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"System Settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1.9m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alfred","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.4m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Control Centre","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.2m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity Monitor","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Preview","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Websites","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Windows","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"UI Events","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"screenpi.pe/onboarding","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.6m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dennikn.sk","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.3m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"screenpi.pe/ideas","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.2m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"nas.lakylak.xyz/desktop/#/","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.2m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"screenpi.pe/why","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.2m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"www.reddit.com/r/software/comments/1fjfwg0/comment/lnr7nqd/","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app.dev.jiminny.com","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"addons.mozilla.org/en-US/firefox/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=find-more-link-bottom","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"screenpi.pe/resources","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"screenpi.pe","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mail.google.com/mail/u/0/#inbox","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"http://localhost:3030","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"addons.mozilla.org/en-US/firefox/addon/proxy-switcher-and-manager/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"www.rtings.com/projector/reviews/dangbei/atom-laser-projector","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"www.reddit.com/r/software/comments/1fjfwg0/screenpipe_open_source_247_screen_audio_capture/","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dennikn.sk/tema/iran/","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dennikn.sk/5268962/sledujte-madarske-volby-ako-experti-toto-su-najdolezitejsie-veci-ktore-treba-vediet/","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"addons.mozilla.org/en-US/firefox/search/?q=profile","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6925687914174941524
|
8117563489471646814
|
visual_change
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Inbox (10) - [EMAIL] - Gmail
Sha DXP4800PLUS-B5F8
Inbox (10) - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Settings
Settings
firefox sidebar - Google Search
firefox sidebar - Google Search
How to use AI-enhanced tab groups | Firefox Help
How to use AI-enhanced tab groups | Firefox Help
Add-ons Manager
Add-ons Manager
Vimium – Get this Extension for 🦊 Firefox (en-US)
Vimium – Get this Extension for 🦊 Firefox (en-US)
dennikn.sk/
dennikn.sk/
Vimium Options
Vimium Options
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Loď Orion úspešne pristála na Zemi. Desať vecí, ktoré si pamätať o misii Artemis II — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Bitwarden Password Manager – Get this Extension for 🦊 Firefox (en-US)
Browser Extension Getting Started | Bitwarden
Browser Extension Getting Started | Bitwarden
Extensions – Add-ons for Firefox (en-US)
Extensions – Add-ons for Firefox (en-US)
Dangbei Atom Review - RTINGS.com
Dangbei Atom Review - RTINGS.com
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Add-ons for Firefox (en-US)
Add-ons for Firefox (en-US)
Problem loading page
Problem loading page
Welcome to Firefox
Welcome to Firefox
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Google Account: Lukáš Koválik ([EMAIL])
Main menu
New chat
Gemini
Temporary chat
PLUS
PLUS
Conversation with Gemini
Conversation with Gemini
Hi Lukáš
Where should we start?
Where should we start?
🖼️ Create image, button, tap to use tool
🖼️ Create image
🎸 Create music, button, tap to use tool
🎸 Create music
Boost my day, button, tap to use tool
Boost my day
Help me learn, button, tap to use tool
Help me learn
Write anything, button, tap to use tool
Write anything
Create a video, button, tap to use tool
Create a video
Dismiss
Try video templates instead of starting from scratch
Try it
Try it
Ask Gemini
Ask Gemini
Open upload file menu
Tools
Open mode picker
Pro
Microphone
Summarize page
Summarize page
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
TOTAL SPAN
12.6h
11:35 → 00:10
ACTIVE
4.6h
BREAKS
3 breaks · 8.0h
SESSIONS
4
63m
87m
114m
117m
340m
FRAMES
2455
APPS
10
UI EVENTS
3628
AUDIO
0
ACTIVE PERIOD
11:35 → 00:10
TIME PER APP
— CLICK TO FILTER RIGHT PANEL
Boosteroid
43.4m
Firefox
9.7m
iTerm2
5.7m
Claude
5.1m
System Settings
1.9m
Alfred
0.4m
Control Centre
0.2m
Code
0.1m
Activity Monitor
0.1m
Preview
0m
Websites
Windows
UI Events
screenpi.pe/onboarding
0.6m
dennikn.sk
0.3m
screenpi.pe/ideas
0.2m
nas.lakylak.xyz/desktop/#/
0.2m
screenpi.pe/why
0.2m
www.reddit.com/r/software/comments/1fjfwg0/comment/lnr7nqd/
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909
0.1m
app.dev.jiminny.com
0.1m
addons.mozilla.org/en-US/firefox/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=find-more-link-bottom
0.1m
screenpi.pe/resources
0.1m
screenpi.pe
0.1m
mail.google.com/mail/u/0/#inbox
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543
0.1m
http://localhost:3030
0.1m
addons.mozilla.org/en-US/firefox/addon/proxy-switcher-and-manager/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search
0.1m
www.rtings.com/projector/reviews/dangbei/atom-laser-projector
0m
www.reddit.com/r/software/comments/1fjfwg0/screenpipe_open_source_247_screen_audio_capture/
0m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37
0m
dennikn.sk/tema/iran/
0m
dennikn.sk/5268962/sledujte-madarske-volby-ako-experti-toto-su-najdolezitejsie-veci-ktore-treba-vediet/
0m
addons.mozilla.org/en-US/firefox/search/?q=profile
0m...
|
7402
|
|
52625
|
1138
|
15
|
2026-04-20T07:25:27.666672+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669927666_m1.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEventTest faVsco.js – TrackAutomatedReportGeneratedEventTest.php...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"11","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
826546130090276540
|
8116237581479545754
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
NULL
|
|
52122
|
1126
|
31
|
2026-04-20T06:40:26.776091+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776667226776_m1.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"role_description":"text"},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
4278925203493857615
|
8116237581412436890
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
52120
|
|
52167
|
1128
|
5
|
2026-04-20T06:42:58.512333+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776667378512_m1.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"role_description":"text"},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
4278925203493857615
|
8116237581412436890
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
NULL
|
|
52170
|
1129
|
7
|
2026-04-20T06:43:23.320881+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776667403320_m2.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","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.796875,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"bounds":{"left":0.8121675,"top":0.019952115,"width":0.103390954,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"role_description":"text"},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.007978723,"height":0.0},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.40492022,"top":0.09896249,"width":0.008643617,"height":0.01915403},"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.41356382,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4245346,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4331782,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.45279256,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4637633,"top":0.09896249,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.49035904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5013298,"top":0.09896249,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6210353332647820543
|
8116237581412436890
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
4
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground...
|
52168
|
|
52626
|
1139
|
23
|
2026-04-20T07:25:27.666989+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669927666_m2.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEventTest faVsco.js – TrackAutomatedReportGeneratedEventTest.php...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
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
27
9
23
3
105
Previous Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","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.796875,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"bounds":{"left":0.8121675,"top":0.019952115,"width":0.103390954,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.36868352,"top":0.2490024,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"11","depth":4,"bounds":{"left":0.37865692,"top":0.2490024,"width":0.008976064,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38929522,"top":0.24740623,"width":0.00731383,"height":0.018355945},"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.39660904,"top":0.24740623,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"bounds":{"left":0.13863032,"top":0.24581006,"width":0.34375,"height":0.75418997},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\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.40492022,"top":0.09896249,"width":0.008643617,"height":0.01915403},"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.41356382,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4245346,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4331782,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.45279256,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4637633,"top":0.09896249,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.49035904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5013298,"top":0.09896249,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.69913566,"top":0.09896249,"width":0.02825798,"height":0.01915403},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"27","depth":4,"bounds":{"left":0.6565825,"top":0.123703115,"width":0.009973404,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.66855055,"top":0.123703115,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"23","depth":4,"bounds":{"left":0.67852396,"top":0.123703115,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.69082445,"top":0.123703115,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"105","depth":4,"bounds":{"left":0.70079786,"top":0.123703115,"width":0.011968086,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7144282,"top":0.12210695,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4369777060296082507
|
8116237581412436890
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
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
27
9
23
3
105
Previous Highlighted Error...
|
NULL
|
|
52654
|
1138
|
30
|
2026-04-20T07:26:30.401408+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669990401_m1.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEventTest faVsco.js – TrackAutomatedReportGeneratedEventTest.php...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
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,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"role_description":"text"},{"role":"AXStaticText","text":"11","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.01875,"height":0.02111111},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5805488249494211579
|
8116237581412436890
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results...
|
52652
|
|
52677
|
1140
|
1
|
2026-04-20T07:29:08.515024+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776670148515_m1.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEventTest faVsco.js – TrackAutomatedReportGeneratedEventTest.php...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
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
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"11","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Listeners\\AutomatedReports\\UserPilot\\TrackAutomatedReportGeneratedEvent;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\nclass TrackAutomatedReportGeneratedEventTest extends TestCase\n{\n private UserPilotClient&MockObject $userPilotClient;\n private AutomatedReportsService&MockObject $automatedReportsService;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->userPilotClient = $this->createMock(UserPilotClient::class);\n $this->automatedReportsService = $this->createMock(AutomatedReportsService::class);\n }\n\n private function makeListener(): TrackAutomatedReportGeneratedEvent\n {\n return new TrackAutomatedReportGeneratedEvent(\n $this->userPilotClient,\n $this->automatedReportsService,\n );\n }\n\n private function makeEvent(AutomatedReport $report): AutomatedReportGenerated\n {\n return new AutomatedReportGenerated($report);\n }\n\n public function testHandleSkipsWhenUserPilotTokenIsNull(): void\n {\n config(['services.userpilot.token' => null]);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->never())->method('isAskJiminnyReport');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksCreatorForAskJiminnyReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn(null);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn('weekly');\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAllRecipientsForExecReport(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $userOne = $this->createMock(User::class);\n $userTwo = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$userOne, $userTwo]);\n\n $this->userPilotClient->expects($this->exactly(2))\n ->method('track')\n ->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {\n $this->assertTrue($user === $userOne || $user === $userTwo);\n $this->assertSame('automated-report-generated', $eventName);\n $this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);\n\n return null;\n });\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('exec_summary');\n $report->expects($this->once())->method('getFrequency')->willReturn('monthly');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->willReturn([]);\n\n $this->userPilotClient->expects($this->never())->method('track');\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleDoesNotThrowOnGuzzleException(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->method('getFrequency')->willReturn('daily');\n\n $guzzleException = $this->createMock(GuzzleException::class);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with($creator, 'ask-jiminny-report-generated', $this->anything())\n ->willThrowException($guzzleException);\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n\n $this->addToAssertionCount(1);\n }\n\n public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $creator = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);\n $report->expects($this->once())->method('getCreator')->willReturn($creator);\n $report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->expects($this->once())->method('getFrequency')->willReturn(null);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $creator,\n 'ask-jiminny-report-generated',\n ['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n\n public function testHandleTracksAutomatedReportWithSingleRecipient(): void\n {\n config(['services.userpilot.token' => 'NX-token']);\n\n $user = $this->createMock(User::class);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);\n $report->expects($this->once())->method('getType')->willReturn('team_performance');\n $report->expects($this->once())->method('getFrequency')->willReturn('daily');\n\n $this->automatedReportsService->expects($this->once())\n ->method('getRecipientUserObjects')\n ->with($report)\n ->willReturn([$user]);\n\n $this->userPilotClient->expects($this->once())\n ->method('track')\n ->with(\n $user,\n 'automated-report-generated',\n ['report_type' => 'team_performance', 'frequency' => 'daily']\n );\n\n $listener = $this->makeListener();\n $listener->handle($this->makeEvent($report));\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"27","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"23","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"105","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1888408852155057749
|
8116237581412436890
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Listeners\AutomatedReports\UserPilot\TrackAutomatedReportGeneratedEvent;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class TrackAutomatedReportGeneratedEventTest extends TestCase
{
private UserPilotClient&MockObject $userPilotClient;
private AutomatedReportsService&MockObject $automatedReportsService;
protected function setUp(): void
{
parent::setUp();
$this->userPilotClient = $this->createMock(UserPilotClient::class);
$this->automatedReportsService = $this->createMock(AutomatedReportsService::class);
}
private function makeListener(): TrackAutomatedReportGeneratedEvent
{
return new TrackAutomatedReportGeneratedEvent(
$this->userPilotClient,
$this->automatedReportsService,
);
}
private function makeEvent(AutomatedReport $report): AutomatedReportGenerated
{
return new AutomatedReportGenerated($report);
}
public function testHandleSkipsWhenUserPilotTokenIsNull(): void
{
config(['services.userpilot.token' => null]);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->never())->method('isAskJiminnyReport');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksCreatorForAskJiminnyReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->automatedReportsService->expects($this->never())->method('getRecipientUserObjects');
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => 'weekly']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleSkipsTrackingWhenAskJiminnyCreatorIsNull(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn(null);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn('weekly');
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAllRecipientsForExecReport(): void
{
config(['services.userpilot.token' => 'NX-token']);
$userOne = $this->createMock(User::class);
$userTwo = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$userOne, $userTwo]);
$this->userPilotClient->expects($this->exactly(2))
->method('track')
->willReturnCallback(function ($user, $eventName, $payload) use ($userOne, $userTwo) {
$this->assertTrue($user === $userOne || $user === $userTwo);
$this->assertSame('automated-report-generated', $eventName);
$this->assertSame(['report_type' => 'exec_summary', 'frequency' => 'monthly'], $payload);
return null;
});
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotTrackWhenExecReportHasNoRecipients(): void
{
config(['services.userpilot.token' => 'NX-token']);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('exec_summary');
$report->expects($this->once())->method('getFrequency')->willReturn('monthly');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->willReturn([]);
$this->userPilotClient->expects($this->never())->method('track');
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleDoesNotThrowOnGuzzleException(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->method('isAskJiminnyReport')->willReturn(true);
$report->method('getCreator')->willReturn($creator);
$report->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->method('getFrequency')->willReturn('daily');
$guzzleException = $this->createMock(GuzzleException::class);
$this->userPilotClient->expects($this->once())
->method('track')
->with($creator, 'ask-jiminny-report-generated', $this->anything())
->willThrowException($guzzleException);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
$this->addToAssertionCount(1);
}
public function testHandleTracksCreatorForAskJiminnyReportWithNullFrequency(): void
{
config(['services.userpilot.token' => 'NX-token']);
$creator = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->exactly(2))->method('isAskJiminnyReport')->willReturn(true);
$report->expects($this->once())->method('getCreator')->willReturn($creator);
$report->expects($this->once())->method('getType')->willReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);
$report->expects($this->once())->method('getFrequency')->willReturn(null);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$creator,
'ask-jiminny-report-generated',
['report_type' => AutomatedReportsService::TYPE_ASK_JIMINNY, 'frequency' => null]
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
public function testHandleTracksAutomatedReportWithSingleRecipient(): void
{
config(['services.userpilot.token' => 'NX-token']);
$user = $this->createMock(User::class);
$report = $this->createMock(AutomatedReport::class);
$report->expects($this->once())->method('isAskJiminnyReport')->willReturn(false);
$report->expects($this->once())->method('getType')->willReturn('team_performance');
$report->expects($this->once())->method('getFrequency')->willReturn('daily');
$this->automatedReportsService->expects($this->once())
->method('getRecipientUserObjects')
->with($report)
->willReturn([$user]);
$this->userPilotClient->expects($this->once())
->method('track')
->with(
$user,
'automated-report-generated',
['report_type' => 'team_performance', 'frequency' => 'daily']
);
$listener = $this->makeListener();
$listener->handle($this->makeEvent($report));
}
}
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
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error...
|
52673
|
|
53792
|
1163
|
41
|
2026-04-20T08:28:20.933717+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776673700933_m2.jpg...
|
PhpStorm
|
faVsco.js – User.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstal...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.796875,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"bounds":{"left":0.8121675,"top":0.019952115,"width":0.103390954,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzing…","depth":4,"bounds":{"left":0.48902926,"top":0.19952115,"width":0.019946808,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.5106383,"top":0.19792499,"width":0.00731383,"height":0.018355945},"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.51795214,"top":0.19792499,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","depth":4,"bounds":{"left":0.14095744,"top":0.1963288,"width":0.38397607,"height":0.8036712},"value":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.9527925,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.96276593,"top":0.10055866,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.09896249,"width":0.00731383,"height":0.018355945},"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.98138297,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","depth":4,"value":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Install","depth":3,"bounds":{"left":0.90957445,"top":0.07821229,"width":0.013297873,"height":0.013567438},"help_text":"Installs packages from composer.json, taking account of composer.lock","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Update","depth":3,"bounds":{"left":0.9281915,"top":0.07821229,"width":0.016289894,"height":0.013567438},"help_text":"Installs latest appropriate versions of packages from composer.json","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show log","depth":3,"bounds":{"left":0.94980055,"top":0.07821229,"width":0.020279255,"height":0.013567438},"help_text":"Show log of Composer-related actions","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"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},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1489337687981201793
|
8112816735717670812
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstal...
|
NULL
|
|
53793
|
1162
|
34
|
2026-04-20T08:28:21.230764+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776673701230_m1.jpg...
|
PhpStorm
|
faVsco.js – User.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","depth":4,"value":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Install","depth":3,"help_text":"Installs packages from composer.json, taking account of composer.lock","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Update","depth":3,"help_text":"Installs latest appropriate versions of packages from composer.json","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show log","depth":3,"help_text":"Show log of Composer-related actions","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6471842230089875170
|
8112816735717670812
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
53791
|
|
54152
|
1168
|
77
|
2026-04-20T08:40:14.936957+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776674414936_m2.jpg...
|
PhpStorm
|
faVsco.js – User.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.796875,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"bounds":{"left":0.8121675,"top":0.019952115,"width":0.103390954,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"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},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"67","depth":4,"bounds":{"left":0.47007978,"top":0.19952115,"width":0.009973404,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.4820479,"top":0.19952115,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"7","depth":4,"bounds":{"left":0.49135637,"top":0.19952115,"width":0.0076462766,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.50099736,"top":0.19952115,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.5106383,"top":0.19792499,"width":0.00731383,"height":0.018355945},"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.51795214,"top":0.19792499,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","depth":4,"bounds":{"left":0.14095744,"top":0.1963288,"width":0.38397607,"height":0.8036712},"value":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"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},"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},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.9527925,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.96276593,"top":0.10055866,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.09896249,"width":0.00731383,"height":0.018355945},"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.98138297,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","depth":4,"value":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Install","depth":3,"bounds":{"left":0.90957445,"top":0.07821229,"width":0.013297873,"height":0.013567438},"help_text":"Installs packages from composer.json, taking account of composer.lock","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Update","depth":3,"bounds":{"left":0.9281915,"top":0.07821229,"width":0.016289894,"height":0.013567438},"help_text":"Installs latest appropriate versions of packages from composer.json","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show log","depth":3,"bounds":{"left":0.94980055,"top":0.07821229,"width":0.020279255,"height":0.013567438},"help_text":"Show log of Composer-related actions","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"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},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1671671177896523754
|
8112816735717670812
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
NULL
|
|
54153
|
1166
|
92
|
2026-04-20T08:40:15.035956+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776674415035_m1.jpg...
|
PhpStorm
|
faVsco.js – User.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"67","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"7","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","depth":4,"value":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Install","depth":3,"help_text":"Installs packages from composer.json, taking account of composer.lock","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Update","depth":3,"help_text":"Installs latest appropriate versions of packages from composer.json","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show log","depth":3,"help_text":"Show log of Composer-related actions","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1671671177896523754
|
8112816735717670812
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
NULL
|
|
54154
|
1166
|
93
|
2026-04-20T08:40:17.983191+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776674417983_m1.jpg...
|
PhpStorm
|
faVsco.js – User.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"67","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"7","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Attributes\\Scope;\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Foundation\\Auth\\User as AuthenticatableUser;\nuse Illuminate\\Notifications\\DatabaseNotificationCollection;\nuse Illuminate\\Notifications\\Notifiable;\nuse Jiminny\\Component\\Model\\BitwiseFlagTrait;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\PlaylistRepository;\nuse Jiminny\\Events\\Users\\GroupChangedEvent;\nuse Jiminny\\Models\\Activity\\AvailabilityNotification;\nuse Jiminny\\Models\\Activity\\CoachRequest;\nuse Jiminny\\Models\\Activity\\Moment;\nuse Jiminny\\Models\\Activity\\Note;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SubscriptionSet;\nuse Jiminny\\Models\\AskAnything\\UserAskAnythingPrompt;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Playlist\\Share;\nuse Jiminny\\Notifications\\Channels\\EmailNotifiableInterface;\nuse Jiminny\\Services\\UserService;\nuse Jiminny\\Traits\\Enums;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Laratrust\\Contracts\\LaratrustUser;\nuse Laratrust\\Traits\\HasRolesAndPermissions;\nuse Laravel\\Passport\\Contracts\\OAuthenticatable;\nuse Laravel\\Passport\\HasApiTokens;\n\n/**\n * Jiminny\\Models\\User\n *\n * @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.\n *\n * @property int $id\n * @property mixed $uuid\n * @property string $name\n * @property string $email\n * @property string|null $secondary_email\n * @property int $status\n * @property string|null $password\n * @property string|null $remember_token\n * @property string|null $photo_path\n * @property bool $uses_two_factor_auth\n * @property string|null $authy_id\n * @property string|null $country_code\n * @property int|null $region_id\n * @property string|null $phone\n * @property string|null $secondary_phone\n * @property string|null $caller_id\n * @property int|null $job_title_id\n * @property string|null $two_factor_reset_code\n * @property int $team_id\n * @property int|null $group_id\n * @property string|null $timezone\n * @property string $language\n * @property string|null $conference_number\n * @property string|null $conference_pin\n * @property string|null $conference_slug\n * @property string $conference_join_preference\n * @property bool $conference_join_reminder\n * @property string $conference_record_announce\n * @property int $conference_record_preference\n * @property int $conference_bandwidth\n * @property int $conference_notify_sms\n * @property int $conference_start_webcam\n * @property int $conference_auto_join_by_computer\n * @property int $conference_reduce_video_resolution\n * @property string|null $softphone_number\n * @property string|null $softphone_inbound_destination\n * @property int $softphone_record_preference\n * @property int $softphone_passthru_pause\n * @property int $softphone_callerid_preference\n * @property bool $softphone_debug\n * @property int|null $transcription_model_locale_id\n * @property string $activity_log_reminder\n * @property bool $activity_action_items\n * @property bool $slack_follow_up\n * @property string $conference_sidekick_open\n * @property string $softphone_sidekick_open\n * @property bool|null $notify_live_coaching\n * @property bool $sync_email\n * @property bool $sync_dialer\n * @property bool $sync_conference\n * @property bool $crm_required\n * @property \\Illuminate\\Support\\Carbon|null $nudges_sent_at\n * @property \\Illuminate\\Support\\Carbon|null $created_at\n * @property \\Illuminate\\Support\\Carbon|null $updated_at\n * @property-read Collection<int, \\Jiminny\\Models\\Activity> $activities\n * @property-read int|null $activities_count\n * @property-read Collection<int, AvailabilityNotification> $availabilityNotifications\n * @property-read int|null $availability_notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Calendar> $calendars\n * @property-read int|null $calendars_count\n * @property-read Collection<int, \\Laravel\\Passport\\Client> $clients\n * @property-read int|null $clients_count\n * @property-read Collection<int, CoachRequest> $coachRequests\n * @property-read int|null $coach_requests_count\n * @property-read Profile|null $crmProfile\n * @property-read Collection<int, \\Jiminny\\Models\\Device> $devices\n * @property-read int|null $devices_count\n * @property-read mixed $first_name\n * @property-read null|string $formatted_softphone_number\n * @property-read string $id_string\n * @property-read string $language_with_hyphen\n * @property-read string $photo_url\n * @property-read \\Jiminny\\Models\\Group|null $group\n * @property-read \\Jiminny\\Models\\Inbox|null $inbox\n * @property-read Collection<int, \\Jiminny\\Models\\Invitation> $invitations\n * @property-read int|null $invitations_count\n * @property-read \\Jiminny\\Models\\JobTitle|null $job\n * @property-read Collection<int, Moment> $moments\n * @property-read int|null $moments_count\n * @property-read Collection<int, Note> $notes\n * @property-read int|null $notes_count\n * @property-read \\Illuminate\\Notifications\\DatabaseNotificationCollection<int, \\Illuminate\\Notifications\\DatabaseNotification> $notifications\n * @property-read int|null $notifications_count\n * @property-read Collection<int, \\Jiminny\\Models\\Nudge> $nudges\n * @property-read int|null $nudges_count\n * @property-read Collection<int, \\Jiminny\\Models\\Participant> $participants\n * @property-read int|null $participants_count\n * @property-read Collection<int, \\Jiminny\\Models\\Permission> $permissions\n * @property-read int|null $permissions_count\n * @property-read Collection<int, \\Jiminny\\Models\\Playlist\\Activity> $playlistActivities\n * @property-read int|null $playlist_activities_count\n * @property-read Collection<int, Share> $playlistSharesFrom\n * @property-read int|null $playlist_shares_from_count\n * @property-read Collection<int, Share> $playlistSharesTo\n * @property-read int|null $playlist_shares_to_count\n * @property-read \\Kalnoy\\Nestedset\\Collection<int, \\Jiminny\\Models\\Playlist> $playlists\n * @property-read int|null $playlists_count\n * @property-read \\Jiminny\\Models\\Region|null $region\n * @property-read Collection<int, \\Jiminny\\Models\\Role> $roles\n * @property-read int|null $roles_count\n * @property-read Collection<int, Search> $searches\n * @property-read int|null $searches_count\n * @property-read Collection<int, \\Jiminny\\Models\\SocialAccount> $socialAccounts\n * @property-read int|null $social_accounts_count\n * @property-read \\Jiminny\\Models\\PhoneNumber|null $softphoneNumberCapabilities\n * @property-read Collection<int, \\Jiminny\\Models\\Activity\\Subscription> $subscribers\n * @property-read int|null $subscribers_count\n * @property-read Collection<int, SubscriptionSet> $subscriptionSets\n * @property-read int|null $subscription_sets_count\n * @property-read \\Jiminny\\Models\\Team $team\n * @property-read Collection<int, \\Laravel\\Passport\\Token> $tokens\n * @property-read int|null $tokens_count\n * @property-read \\Jiminny\\Models\\TranscriptionModelLocale|null $transcriptionModelLocale\n * @property-read DatabaseNotificationCollection $unreadNotifications\n *\n * @method static Builder|User active()\n * @method static \\Database\\Factories\\UserFactory factory(...$parameters)\n * @method static Builder|User idOrUuId($idOrUuid, bool $first = true)\n * @method static Builder|User newModelQuery()\n * @method static Builder|User newQuery()\n * @method static Builder|User orWhereHasPermission($permission = '')\n * @method static Builder|User orWhereHasRole($role = '', $team = null)\n * @method static Builder|User query()\n * @method static Builder|User shouldSyncDialers()\n * @method static Builder|User uuid(string $uuid, bool $first = true)\n * @method static Builder|User whereActivityActionItems($value)\n * @method static Builder|User whereActivityLogReminder($value)\n * @method static Builder|User whereAuthyId($value)\n * @method static Builder|User whereCallerId($value)\n * @method static Builder|User whereConferenceAutoJoinByComputer($value)\n * @method static Builder|User whereConferenceBandwidth($value)\n * @method static Builder|User whereConferenceJoinPreference($value)\n * @method static Builder|User whereConferenceJoinReminder($value)\n * @method static Builder|User whereConferenceNotifySms($value)\n * @method static Builder|User whereConferenceNumber($value)\n * @method static Builder|User whereConferencePin($value)\n * @method static Builder|User whereConferenceRecordAnnounce($value)\n * @method static Builder|User whereConferenceRecordPreference($value)\n * @method static Builder|User whereConferenceReduceVideoResolution($value)\n * @method static Builder|User whereConferenceSidekickOpen($value)\n * @method static Builder|User whereConferenceSlug($value)\n * @method static Builder|User whereConferenceStartWebcam($value)\n * @method static Builder|User whereCountryCode($value)\n * @method static Builder|User whereCreatedAt($value)\n * @method static Builder|User whereCrmRequired($value)\n * @method static Builder|User whereDeletedAt($value)\n * @method static Builder|User whereDoesntHavePermission()\n * @method static Builder|User whereDoesntHaveRoles()\n * @method static Builder|User whereEmail($value)\n * @method static Builder|User whereGroupId($value)\n * @method static Builder|User whereId($value)\n * @method static Builder|User whereJobTitleId($value)\n * @method static Builder|User whereLanguage($value)\n * @method static Builder|User whereName($value)\n * @method static Builder|User whereNotifyLiveCoaching($value)\n * @method static Builder|User wherePassword($value)\n * @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')\n * @method static Builder|User wherePhone($value)\n * @method static Builder|User wherePhotoPath($value)\n * @method static Builder|User whereRegionId($value)\n * @method static Builder|User whereRememberToken($value)\n * @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')\n * @method static Builder|User whereSecondaryEmail($value)\n * @method static Builder|User whereSecondaryPhone($value)\n * @method static Builder|User whereSoftphoneCalleridPreference($value)\n * @method static Builder|User whereSoftphoneDebug($value)\n * @method static Builder|User whereSoftphoneInboundDestination($value)\n * @method static Builder|User whereSoftphoneNumber($value)\n * @method static Builder|User whereSoftphonePassthruPause($value)\n * @method static Builder|User whereSoftphoneRecordPreference($value)\n * @method static Builder|User whereSoftphoneSidekickOpen($value)\n * @method static Builder|User whereStatus($value)\n * @method static Builder|User whereSyncConference($value)\n * @method static Builder|User whereSyncDialer($value)\n * @method static Builder|User whereSyncEmail($value)\n * @method static Builder|User whereTeamId($value)\n * @method static Builder|User whereTimezone($value)\n * @method static Builder|User whereTranscriptionModelLocaleId($value)\n * @method static Builder|User whereTwoFactorResetCode($value)\n * @method static Builder|User whereUpdatedAt($value)\n * @method static Builder|User whereUsesTwoFactorAuth($value)\n * @method static Builder|User whereUuid($value)\n * @method static activeForTeam(int $teamId)\n *\n * @mixin \\Eloquent\n */\nclass User extends AuthenticatableUser implements\n EmailNotifiableInterface,\n UserContract,\n LaratrustUser,\n OAuthenticatable\n{\n use HasFactory;\n use HasApiTokens;\n use HasRolesAndPermissions;\n use RequiresUUID;\n use Enums;\n use Notifiable;\n use BitwiseFlagTrait;\n use Authorizable;\n\n public const string ACTIVITY_LOG_REMINDER_OFF = 'off';\n public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';\n public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';\n\n public const int STATUS_ACTIVE = 1;\n public const int STATUS_DEACTIVATED = 2;\n\n public const string ROLE_ADMIN = 'admin';\n public const string ROLE_MANAGER = 'manager';\n public const string ROLE_RECORDER = 'recorder';\n public const string ROLE_ANALYST = 'analyst';\n public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';\n public const string ROLE_LISTENER = 'listener';\n\n public const array MANAGER_ROLES = [\n self::ROLE_MANAGER,\n self::ROLE_ADMIN,\n ];\n\n public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;\n\n public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;\n public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;\n\n // Log activity to CRM reminder\n\n public array $enumActivityLogReminder = [\n self::ACTIVITY_LOG_REMINDER_OFF,\n self::ACTIVITY_LOG_REMINDER_SLACK,\n self::ACTIVITY_LOG_REMINDER_EMAIL,\n ];\n\n protected $fillable = [\n 'name',\n 'email',\n 'secondary_email',\n 'photo_path',\n 'phone',\n 'secondary_phone',\n 'caller_id',\n 'country_code',\n 'region_id',\n 'timezone',\n 'language',\n 'conference_slug', // XXX: To be removed.\n 'conference_record_preference',\n 'conference_join_reminder',\n 'softphone_number',\n 'softphone_inbound_destination',\n 'softphone_record_preference',\n 'softphone_passthru_pause',\n 'softphone_debug',\n 'team_id',\n 'group_id',\n 'status',\n 'job_title_id',\n 'transcription_model_locale_id',\n 'notify_live_coaching',\n 'activity_log_reminder',\n 'conference_sidekick_open',\n 'softphone_sidekick_open',\n 'activity_action_items',\n 'slack_follow_up',\n 'sync_email',\n 'sync_conference',\n 'sync_dialer',\n 'crm_required',\n 'nudges_sent_at',\n ];\n\n /**\n * The attributes excluded from the model's JSON form.\n */\n protected $hidden = [\n 'uuid',\n 'password',\n 'remember_token',\n 'authy_id',\n 'uses_two_factor_auth',\n 'two_factor_reset_code',\n ];\n\n protected $appends = [\n 'id_string',\n 'formatted_softphone_number',\n 'photo_url',\n ];\n\n protected $visible = [\n 'id_string',\n 'team',\n 'group',\n 'name',\n 'email',\n 'role',\n 'photo_url',\n 'phone',\n 'secondary_phone',\n 'photo_path',\n 'country_code',\n 'timezone',\n 'language',\n 'conference_slug',\n 'status',\n 'softphone_record_preference',\n 'conference_record_preference',\n 'job',\n ];\n\n protected $observables = ['activated', 'deactivated'];\n\n /**\n * Get the attributes that should be cast.\n *\n * @return array<string, string>\n */\n protected function casts(): array\n {\n return [\n 'created_at' => 'datetime',\n 'nudges_sent_at' => 'datetime',\n 'uses_two_factor_auth' => 'boolean',\n 'conference_record_preference' => 'integer',\n 'conference_join_reminder' => 'boolean',\n 'softphone_record_preference' => 'integer',\n 'softphone_debug' => 'boolean',\n 'notify_live_coaching' => 'boolean',\n 'activity_action_items' => 'boolean',\n 'slack_follow_up' => 'boolean',\n 'sync_email' => 'boolean',\n 'sync_conference' => 'boolean',\n 'sync_dialer' => 'boolean',\n 'crm_required' => 'boolean',\n ];\n }\n\n public function activate(): void\n {\n $this->update(['status' => self::STATUS_ACTIVE]);\n $this->fireModelEvent('activated', false);\n }\n\n public function deactivate(): void\n {\n $this->update(['status' => self::STATUS_DEACTIVATED]);\n $this->fireModelEvent('deactivated', false);\n }\n\n public function getIndexableAttributes(?array $loadRelations = null): array\n {\n $attributes = $this->attributesToArray();\n\n $loadRelationsDefault = [\n 'team',\n 'job',\n 'roles',\n 'group',\n ];\n\n $relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)\n ? $loadRelations\n : $loadRelationsDefault;\n\n $relations = [];\n\n if (in_array('team', $relationsToLoad, true)) {\n $relations['team'] = $this->getTeam()->getIndexableAttributes();\n }\n\n if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {\n $relations['job'] = $jobTitle->getIndexableAttributes();\n }\n\n if (in_array('roles', $relationsToLoad, true)) {\n $relations['roles'] = $this->getAttribute('roles')\n ->map(static function (Role $role): array {\n return $role->getIndexableAttributes();\n })\n ->all();\n }\n\n $group = $this->getGroup();\n if ($group !== null && in_array('group', $relationsToLoad, true)) {\n $relations['group'] = $group->getIndexableAttributes();\n }\n\n return array_merge($attributes, $relations);\n }\n\n public function shouldSyncDialer(): bool\n {\n return $this->isStatusActive()\n && $this->hasPermission(PermissionEnum::RECORD_MEETING)\n && $this->sync_dialer;\n }\n\n public function shouldSyncCalendarEvents(): bool\n {\n return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;\n }\n\n /**\n * The channels the user receives notification broadcasts on.\n */\n public function receivesBroadcastNotificationsOn(): string\n {\n return 'user.' . $this->id_string;\n }\n\n /**\n * Route notifications for the Slack channel.\n */\n public function routeNotificationForSlack(): ?string\n {\n return $this->team->slackBot->webhook_url;\n }\n\n /**\n * Always lowercase the slug when we save it to the database.\n */\n public function setConferenceSlugAttribute(?string $value): void\n {\n $this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;\n }\n\n /**\n * Display in national format e.g. (617) 124-1411.\n */\n public function getFormattedSoftphoneNumberAttribute(): ?string\n {\n return phone_national($this->country_code, $this->softphone_number);\n }\n\n /**\n * Get the url to user photo.\n *\n * @deprecated - it's depentent on Elastic search\n */\n public function getPhotoUrlAttribute(): ?string\n {\n if (empty($this->photo_path)) {\n return null;\n }\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $this->photo_path)) {\n return $this->photo_path;\n }\n\n // Prepend cdn base url to photo uri\n return client_cdn($this->photo_path, $this->team);\n }\n\n public function getPhotoUrl(): ?string\n {\n $photoPath = $this->photo_path;\n\n // If user photo_path column in DB is already a fully qualified URL.\n if (preg_match('/^https?:\\/\\//i', $photoPath)) {\n return $photoPath;\n }\n\n // Prepend cdn base url to photo uri\n return $photoPath ? client_cdn($photoPath, $this->team) : null;\n }\n\n /**\n * Get the activity log to CRM reminder setting, deferring to team for control.\n */\n public function getActivityLogReminderAttribute($value): string\n {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n\n switch ($this->team->activity_log_reminder) {\n case Team::ACTIVITY_LOG_REMINDER_OFF:\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_ON:\n // When organization setting is set to ON the user can't set it to OFF\n if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {\n $activityLogReminder = $value;\n } else {\n $activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;\n }\n\n break;\n case Team::ACTIVITY_LOG_REMINDER_DELEGATE:\n $activityLogReminder = $value;\n\n break;\n }\n\n return $activityLogReminder;\n }\n\n public function getLanguageWithHyphenAttribute(): string\n {\n return str_replace('_', '-', $this->language);\n }\n\n /**\n * Rooms can be accessed from our parent domain.\n */\n public function getFirstNameAttribute()\n {\n return fullNameToFirstName($this->name);\n }\n\n public function getFirstName(): string\n {\n return $this->getAttribute('first_name');\n }\n\n public function getTimezone(): \\DateTimeZone\n {\n try {\n return new \\DateTimeZone($this->getAttribute('timezone'));\n } catch (\\Exception $e) {\n // Get from their team instead.\n return $this->getTeam()->getTimezone();\n }\n }\n\n public function getTimezoneOffset(): string\n {\n return now()->setTimezone($this->getTimezone())->format('P');\n }\n\n public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable\n {\n if ($dateTime !== null) {\n return $dateTime->setTimezone($this->getTimezone());\n }\n\n return CarbonImmutable::now($this->getTimezone());\n }\n\n public function getSocialAccount(string $provider): ?SocialAccount\n {\n /** @var SocialAccount|null */\n return $this->socialAccounts()\n ->where('provider', $provider)\n ->first();\n }\n\n public function hasSocialAccount(string $providerName): bool\n {\n return $this->socialAccounts()\n ->where('provider', $providerName)\n ->exists();\n }\n\n public function getStatus(): int\n {\n return $this->getAttribute('status');\n }\n\n public function isStatus(int $status): bool\n {\n return $this->getStatus() === $status;\n }\n\n public function isStatusActive(): bool\n {\n return $this->isStatus(self::STATUS_ACTIVE);\n }\n\n public function isOrganizer(Activity $activity): bool\n {\n return $this->id && $this->id === $activity->id;\n }\n\n /** @return HasOne<Profile> */\n public function crmProfile(): HasOne\n {\n return $this->hasOne(Profile::class);\n }\n\n public function job()\n {\n return $this->hasOne(JobTitle::class, 'id', 'job_title_id');\n }\n\n public function devices()\n {\n return $this->hasMany(Device::class);\n }\n\n /**\n * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo|Region\n */\n public function region()\n {\n return $this->belongsTo(Region::class);\n }\n\n /**\n * Get all of the user's followers.\n */\n public function subscribers(): MorphMany\n {\n return $this->morphMany(Activity\\Subscription::class, 'followable');\n }\n\n /** @return MorphMany<RoleChangeEvent> */\n public function roleChangeEvents(): MorphMany\n {\n return $this->morphMany(RoleChangeEvent::class, 'affected');\n }\n\n /**\n * Get all of their subscriptions sets.\n */\n public function subscriptionSets()\n {\n return $this->hasMany(SubscriptionSet::class);\n }\n\n /**\n * @return HasMany<SocialAccount>\n */\n public function socialAccounts(): HasMany\n {\n /** @var HasMany<SocialAccount> */\n return $this->hasMany(SocialAccount::class, 'sociable_id');\n }\n\n /**\n * Get all of the pending invitations for the user.\n */\n public function invitations()\n {\n return $this->hasMany(Invitation::class);\n }\n\n public function calendars(): HasMany\n {\n return $this->hasMany(Calendar::class);\n }\n\n public function inbox(): HasOne\n {\n return $this->hasOne(Inbox::class);\n }\n\n public function moments(): HasMany\n {\n return $this->hasMany(Moment::class);\n }\n\n public function activities(): HasMany\n {\n return $this->hasMany(Activity::class);\n }\n\n public function notes()\n {\n return $this->hasMany(Note::class);\n }\n\n public function participants()\n {\n return $this->hasMany(Participant::class);\n }\n\n public function coachRequests()\n {\n return $this->hasMany(CoachRequest::class);\n }\n\n public function availabilityNotifications()\n {\n return $this->hasMany(AvailabilityNotification::class);\n }\n\n /** @return BelongsTo<Group, self> */\n public function group(): BelongsTo\n {\n return $this->belongsTo(Group::class);\n }\n\n /** @return BelongsTo<Team, self> */\n public function team(): BelongsTo\n {\n return $this->belongsTo(Team::class);\n }\n\n public function playlists()\n {\n return $this->hasMany(Playlist::class, 'owner_id');\n }\n\n public function playlistActivities()\n {\n return $this->hasMany(Playlist\\Activity::class);\n }\n\n /**\n * Playlist shares from this user.\n */\n public function playlistSharesFrom(): HasMany\n {\n return $this->hasMany(Share::class, 'from_user_id');\n }\n\n /**\n * Playlist shares to this user.\n */\n public function playlistSharesTo(): HasMany\n {\n return $this->hasMany(Share::class, 'to_user_id');\n }\n\n /**\n * Returns user's favorite playlist. Creates one if it doesn't exist.\n */\n public function favoritePlaylist(): Playlist\n {\n return app(PlaylistRepository::class)->getDefaultPlaylist($this);\n }\n\n #[Scope]\n protected function active($query)\n {\n return $query->where('status', self::STATUS_ACTIVE);\n }\n\n #[Scope]\n protected function admin($query)\n {\n return $query->whereHas('roles', static function (Builder $query): void {\n $query->whereIn('roles.name', [\n User::ROLE_ADMIN,\n ]);\n });\n }\n\n #[Scope]\n protected function shouldSyncDialers($query)\n {\n return $query\n ->whereHasPermission([PermissionEnum::RECORD_MEETING])\n ->where('status', self::STATUS_ACTIVE)\n ->where('sync_dialer', true);\n }\n\n #[Scope]\n protected function activeForTeam($query, int $teamId)\n {\n return $query->where('status', self::STATUS_ACTIVE)\n ->where('team_id', $teamId);\n }\n\n public function transcriptionModelLocale(): BelongsTo\n {\n return $this->belongsTo(TranscriptionModelLocale::class);\n }\n\n public function searches(): HasMany\n {\n return $this->hasMany(Search::class);\n }\n\n public function nudges(): HasManyThrough\n {\n return $this->hasManyThrough(\n Nudge::class,\n Search::class,\n 'user_id',\n 'activity_search_id'\n );\n }\n\n public function askAnythingPrompts(): HasMany\n {\n return $this->hasMany(UserAskAnythingPrompt::class);\n }\n\n /**\n * Determine if the user is on the given team.\n */\n public function onTeam(Team $team): bool\n {\n return $this->team_id === $team->id;\n }\n\n /**\n * Determine if the given team is owned by the user.\n */\n public function ownsTeam(?Team $team = null): bool\n {\n return $this->getId() === ($team ?? $this->getTeam())->owner_id;\n }\n\n /**\n * Determine if the user is in the given group.\n */\n public function inGroup(Group $group): bool\n {\n return $this->group && $this->group_id === $group->id;\n }\n\n /**\n * Determine if the given group is owned by the user.\n */\n public function ownsGroup(Group $group): bool\n {\n return $this->id && $group->user_id && $this->id === $group->user_id;\n }\n\n /**\n * Switch the current group for the user.\n */\n public function switchToGroup(Group $group)\n {\n if ($this->inGroup($group)) {\n throw new \\InvalidArgumentException('The user is already in the given group.');\n }\n\n $this->update(['group_id' => $group->id]);\n\n event(new GroupChangedEvent($this));\n }\n\n public function generateHash(): string\n {\n $hash = hash_hmac(\n 'sha256',\n $this->id_string,\n config('app.key')\n );\n\n return $hash;\n }\n\n public function isHashValid(string $hash): bool\n {\n return $hash === $this->generateHash();\n }\n\n /**\n * @deprecated Use Activity::findParticipant() instead.\n */\n public function getParticipantByActivity(Activity $activity): Participant\n {\n return $activity->getParticipant($this);\n }\n\n public function softphoneNumberCapabilities(): HasOne\n {\n return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');\n }\n\n public function hasSoftphoneNumberCapabilities(): bool\n {\n return $this->getAttribute('softphoneNumberCapabilities') !== null;\n }\n\n public function getSoftphoneNumberCapabilities(): PhoneNumber\n {\n return $this->getAttribute('softphoneNumberCapabilities');\n }\n\n public function checkConferenceRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isConferenceRecordPreferenceEnabled() ||\n ($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())\n );\n }\n\n public function getConferenceRecordPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordPreferenceEnabled(): bool\n {\n return $this->getConferenceRecordPreference();\n }\n\n public function checkConferenceRecordInternalPreference(): bool\n {\n $team = $this->team;\n\n if ($team->isConferenceRecordPreferenceEnabled()) {\n return $team->isConferenceRecordInternalPreferenceEnabled();\n }\n\n return $this->isConferenceRecordInternalPreferenceEnabled();\n }\n\n private function getConferenceRecordInternalPreference(): bool\n {\n return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);\n }\n\n public function setConferenceRecordInternalPreference(bool $isEnabled): self\n {\n return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isConferenceRecordInternalPreferenceEnabled(): bool\n {\n $team = $this->getTeam();\n\n return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();\n }\n\n /**\n * Check if should record softphone\n */\n public function checkSoftphoneOutboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneOutboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneOutboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneOutboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneOutboundRecordPreference();\n }\n\n public function checkSoftphoneInboundRecordPreference(): bool\n {\n $team = $this->team;\n\n return (\n $team->isSoftphoneInboundRecordPreferenceEnabled() ||\n ($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())\n );\n }\n\n public function getSoftphoneInboundRecordPreference(): bool\n {\n return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);\n }\n\n public function setSoftphoneInboundRecordPreference(bool $isEnabled): self\n {\n return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);\n }\n\n public function isSoftphoneInboundRecordPreferenceEnabled(): bool\n {\n return $this->getSoftphoneInboundRecordPreference();\n }\n\n /**\n * GETTERS AND SETTERS FOLLOW BELOW THIS LINE\n */\n public function getId(): int\n {\n return $this->getAttribute('id');\n }\n\n /** @deprecated use getUuid */\n public function getIdString(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getName(): string\n {\n return $this->getAttribute('name');\n }\n\n public function setName(string $name): self\n {\n $this->setAttribute('name', $name);\n\n return $this;\n }\n\n public function getEmailAddress(): string\n {\n return $this->getAttribute('email');\n }\n\n public function setEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('email', $emailAddress);\n\n return $this;\n }\n\n public function getSecondaryEmailAddress(): ?string\n {\n return $this->getAttribute('secondary_email');\n }\n\n public function setSecondaryEmailAddress(string $emailAddress): self\n {\n $this->setAttribute('secondary_email', $emailAddress);\n\n return $this;\n }\n\n public function getTranscriptionModelLocale(): TranscriptionModelLocale\n {\n return $this->getAttribute('transcriptionModelLocale');\n }\n\n public function hasTranscriptionModelLocale(): bool\n {\n return $this->getAttribute('transcriptionModelLocale') !== null;\n }\n\n public function getGroup(): ?Group\n {\n /** @var Group|null */\n return $this->getAttribute('group');\n }\n\n public function getGroupId(): ?int\n {\n return $this->getAttribute('group_id');\n }\n\n public function hasGroupId(): bool\n {\n return $this->getAttribute('group_id') !== null;\n }\n\n public function isSameGroupId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $authenticatable->hasGroupId() && $this->hasGroupId()\n && $this->getGroupId() === $authenticatable->getGroupId();\n }\n\n public function getTeamId(): int\n {\n return $this->getAttribute('team_id');\n }\n\n public function isSameTeamId(Authenticatable $authenticatable): bool\n {\n return $authenticatable instanceof self\n && $this->getTeamId() === $authenticatable->getTeamId();\n }\n\n public function getTeam(): Team\n {\n return $this->getAttribute('team');\n }\n\n public function hasTeam(): bool\n {\n return $this->getAttribute('team') !== null;\n }\n\n public function getJobTitle(): ?JobTitle\n {\n /** @var JobTitle|null */\n return $this->job;\n }\n\n public function getCountryCode(): ?string\n {\n return $this->getAttribute('country_code');\n }\n\n public function isSame(self $user): bool\n {\n return $this->getId() === $user->getId();\n }\n\n public function getLanguage(): string\n {\n return $this->getAttribute('language');\n }\n\n public function setLanguage(string $language): self\n {\n $this->setAttribute('language', $language);\n\n return $this;\n }\n\n public function hasCallerId(): bool\n {\n return $this->getAttribute('caller_id') !== null;\n }\n\n public function getCallerId(): ?string\n {\n return $this->getAttribute('caller_id');\n }\n\n public function getSoftPhoneNumber(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('softphone_number');\n }\n\n public function getPhone(): ?string\n {\n return $this->phone;\n }\n\n public function getSecondaryPhone(): ?string\n {\n /** @var string|null */\n return $this->getAttribute('secondary_phone');\n }\n\n public function hasSoftPhoneInboundDestinationNumber(): bool\n {\n return $this->getAttribute('softphone_inbound_destination') !== null;\n }\n\n public function getSoftPhoneInboundDestinationNumber(): string\n {\n return $this->getAttribute('softphone_inbound_destination');\n }\n\n public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self\n {\n return $this->setAttribute('softphone_inbound_destination', $phoneNumber);\n }\n\n public function getRegion(): Region\n {\n return $this->getAttribute('region');\n }\n\n public function hasRegion(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function hasRegionId(): bool\n {\n return $this->getAttribute('region_id') !== null;\n }\n\n public function getRegionId(): ?int\n {\n return $this->getAttribute('region_id');\n }\n\n public function setRegionId(?int $regionId): self\n {\n $this->setAttribute('region_id', $regionId);\n\n return $this;\n }\n\n public function getSidekickLaunchSettingsForMeeting(): string\n {\n return $this->getConferenceSidekickOpen();\n }\n\n public function getSidekickLaunchSettingsForCall(): string\n {\n return $this->getSoftphoneSidekickOpen();\n }\n\n public function getConferenceSidekickOpen(): ?string\n {\n return $this->getAttribute('conference_sidekick_open');\n }\n\n public function getSoftphoneSidekickOpen(): ?string\n {\n return $this->getAttribute('softphone_sidekick_open');\n }\n\n public function isActivityActionItemsEnabled(): bool\n {\n return $this->getAttribute('activity_action_items');\n }\n\n public function isSlackFollowUpEnabled(): bool\n {\n return $this->getAttribute('slack_follow_up');\n }\n\n public function disableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', false);\n\n return $this;\n }\n\n public function enableSlackFollowUp(): self\n {\n $this->setAttribute('slack_follow_up', true);\n\n return $this;\n }\n\n public function getRecipientEmail(): string\n {\n return $this->getEmailAddress();\n }\n\n public function getTeamContext(): ?Team\n {\n return $this->hasTeam() ? $this->getTeam() : null;\n }\n\n public function getInbox(): ?Inbox\n {\n return $this->getAttribute('inbox');\n }\n\n public function isSyncEmailEnabled(): bool\n {\n return $this->getAttribute('sync_email') === true;\n }\n\n public function isCrmRequired(): bool\n {\n return $this->getAttribute('crm_required');\n }\n\n public function getUuid(): string\n {\n return $this->getAttribute('id_string');\n }\n\n public function getProfile(): ?Profile\n {\n /** @var Profile */\n return $this->getAttribute('crmProfile');\n }\n\n public function isTeamOwner(): bool\n {\n return $this->getId() === $this->getTeam()->owner_id;\n }\n\n public function isAdmin(): bool\n {\n return $this->hasRole(self::ROLE_ADMIN);\n }\n\n public function getCreatedAt(): Carbon\n {\n return $this->getAttribute('created_at');\n }\n\n public function getNudgesSentAt(): ?Carbon\n {\n return $this->getAttribute('nudges_sent_at');\n }\n\n public function languageDialects(): BelongsToMany\n {\n return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')\n ->withTimestamps();\n }\n\n /**\n * @return Collection<int, LanguageDialect>\n */\n public function getLanguageDialects(): Collection\n {\n return $this->getAttribute('languageDialects');\n }\n\n public function getDateTimeFormat(?string $format = null): string\n {\n return app(UserService::class)->getDateFormat($this, $format);\n }\n\n public function getConferenceSlug(): ?string\n {\n return $this->getAttribute('conference_slug');\n }\n\n public function isSyncConferenceEnabled(): bool\n {\n return $this->getAttribute('sync_conference');\n }\n\n public function getPhotoPath(): ?string\n {\n return $this->getAttribute('photo_path');\n }\n\n public function setPhotoPath(?string $path): self\n {\n $this->setAttribute('photo_path', $path);\n\n return $this;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","depth":4,"value":"{\n \"name\": \"jiminny/app\",\n \"description\": \"The Jiminny Platform.\",\n \"keywords\": [\n \"training\",\n \"salesforce\",\n \"conference\"\n ],\n \"license\": \"MIT\",\n \"type\": \"project\",\n \"require\": {\n \"php\": \"^8.3\",\n \"ext-ctype\": \"*\",\n \"ext-curl\": \"*\",\n \"ext-date\": \"*\",\n \"ext-dom\": \"*\",\n \"ext-fileinfo\": \"*\",\n \"ext-filter\": \"*\",\n \"ext-gd\": \"*\",\n \"ext-gmp\": \"*\",\n \"ext-hash\": \"*\",\n \"ext-iconv\": \"*\",\n \"ext-igbinary\": \"*\",\n \"ext-imagick\": \"*\",\n \"ext-intl\": \"*\",\n \"ext-json\": \"*\",\n \"ext-libxml\": \"*\",\n \"ext-mailparse\": \"*\",\n \"ext-mbstring\": \"*\",\n \"ext-mysqlnd\": \"*\",\n \"ext-openssl\": \"*\",\n \"ext-pcntl\": \"*\",\n \"ext-pcre\": \"*\",\n \"ext-pdo\": \"*\",\n \"ext-pdo_mysql\": \"*\",\n \"ext-phar\": \"*\",\n \"ext-phpiredis\": \"*\",\n \"ext-posix\": \"*\",\n \"ext-readline\": \"*\",\n \"ext-redis\": \"*\",\n \"ext-reflection\": \"*\",\n \"ext-session\": \"*\",\n \"ext-simplexml\": \"*\",\n \"ext-sockets\": \"*\",\n \"ext-spl\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xml\": \"*\",\n \"ext-xmlreader\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"ext-zend-opcache\": \"*\",\n \"ext-zip\": \"*\",\n \"ext-zlib\": \"*\",\n \"lib-curl\": \"*\",\n \"lib-curl-openssl\": \"*\",\n \"lib-curl-zlib\": \"*\",\n \"lib-date-timelib\": \"*\",\n \"lib-date-zoneinfo\": \"*\",\n \"lib-fileinfo-libmagic\": \"*\",\n \"lib-gd\": \"*\",\n \"lib-gd-freetype\": \"*\",\n \"lib-gd-libjpeg\": \"*\",\n \"lib-gd-libpng\": \"*\",\n \"lib-gmp\": \"*\",\n \"lib-icu\": \"*\",\n \"lib-icu-cldr\": \"*\",\n \"lib-icu-unicode\": \"*\",\n \"lib-imagick-imagemagick\": \"*\",\n \"lib-libxml\": \"*\",\n \"lib-mbstring-libmbfl\": \"*\",\n \"lib-mbstring-oniguruma\": \"*\",\n \"lib-openssl\": \"*\",\n \"lib-pcre\": \"*\",\n \"lib-pcre-unicode\": \"*\",\n \"lib-zip-libzip\": \"*\",\n \"lib-zlib\": \"*\",\n \"24slides/laravel-saml2\": \"^2.4\",\n \"adam-paterson/oauth2-slack\": \"^1.1\",\n \"asimlqt/php-google-spreadsheet-client\": \"^3.0\",\n \"aws/aws-sdk-php\": \"^3.368\",\n \"aws/aws-sdk-php-laravel\": \"^3.10\",\n \"bepsvpt/secure-headers\": \"^9.0\",\n \"chadhutchins/oauth2-slack\": \"^1.2\",\n \"chaseconey/laravel-datadog-helper\": \"^1.2\",\n \"chrisyue/php-m3u8\": \"4.0.3\",\n \"daniti/oauth2-pipedrive\": \"dev-master\",\n \"devio/pipedrive\": \"^2.6\",\n \"doctrine/dbal\": \"^4.0\",\n \"elasticsearch/elasticsearch\": \"^7.11\",\n \"erusev/parsedown\": \"^1.7\",\n \"fakerphp/faker\": \"^1.23\",\n \"firebase/php-jwt\": \"^7.0\",\n \"flipboxdigital/oauth2-hubspot\": \"1.0.1\",\n \"giggsey/libphonenumber-for-php\": \"^8.12\",\n \"google/apiclient\": \"^2.19\",\n \"google/apiclient-services\": \"~0.360\",\n \"google/apps-meet\": \"^0.5.1\",\n \"guzzlehttp/guzzle\": \"^7.8\",\n \"guzzlehttp/psr7\": \"^2.6\",\n \"halaxa/json-machine\": \"^1.2\",\n \"html2text/html2text\": \"^4.3\",\n \"hubspot/api-client\": \"~5.0.0\",\n \"hubspot/hubspot-php\": \"^5.2.0\",\n \"intercom/intercom-php\": \"^4.5\",\n \"intervention/image\": \"^3.4\",\n \"jakeasmith/http_build_url\": \"^1.0\",\n \"jdavidbakr/cloudfront-proxies\": \"^1.7\",\n \"jeremykendall/php-domain-parser\": \"^6.3\",\n \"jiminny/oauth2-aircall\": \"dev-master\",\n \"jiminny/oauth2-bullhorn\": \"^0.2.0\",\n \"jiminny/oauth2-dialpad\": \"dev-master\",\n \"jiminny/oauth2-salesloft\": \"dev-master\",\n \"jolicode/slack-php-api\": \"^4.5.0\",\n \"kalnoy/nestedset\": \"*\",\n \"laravel/framework\": \"^12.28\",\n \"laravel/helpers\": \"^1.7\",\n \"laravel/passport\": \"^13.0\",\n \"laravel/slack-notification-channel\": \"^3.4\",\n \"laravel/tinker\": \"^2.10.1\",\n \"laravel/ui\": \"^4.6\",\n \"laravolt/avatar\": \"^6.1\",\n \"league/flysystem\": \"^3.0\",\n \"league/flysystem-aws-s3-v3\": \"^3.0\",\n \"league/fractal\": \"*\",\n \"league/oauth2-client\": \"^2.7\",\n \"league/oauth2-google\": \"^4.0\",\n \"league/oauth2-linkedin\": \"^5.1\",\n \"league/oauth2-server\": \"^9.2\",\n \"league/statsd\": \"^2.0\",\n \"markrogoyski/math-php\": \"^2.7.0\",\n \"microsoft/microsoft-graph\": \"^2.51\",\n \"monolog/monolog\": \"^3.0\",\n \"nesbot/carbon\": \"^3.8\",\n \"nette/caching\": \"*\",\n \"phlib/sms-length\": \"^2.0\",\n \"php-ffmpeg/php-ffmpeg\": \"^1.2\",\n \"php-http/client-common\": \"^2.7\",\n \"php-http/curl-client\": \"^2.3\",\n \"php-http/httplug\": \"^2.2\",\n \"php-http/message\": \"^1.16\",\n \"phpseclib/phpseclib\": \"^3.0.36\",\n \"propaganistas/laravel-phone\": \"^5.3\",\n \"psr/cache\": \"^3.0\",\n \"psr/http-message\": \"^2.0\",\n \"psr/log\": \"^3.0\",\n \"psr/simple-cache\": \"^3.0\",\n \"pusher/pusher-php-server\": \"7.2.3\",\n \"ramsey/uuid\": \"^4.2\",\n \"ringcentral/ringcentral-php\": \"3.0.0\",\n \"rmccue/requests\": \"^2.0\",\n \"ruflin/elastica\": \"^7.1.1\",\n \"santigarcor/laratrust\": \"^8.4\",\n \"sentry/sentry\": \"4.13.0\",\n \"sentry/sentry-laravel\": \"~4.13.0\",\n \"shiftonelabs/laravel-sqs-fifo-queue\": \"^3.0\",\n \"spatie/fractalistic\": \"^2.9\",\n \"spatie/laravel-fractal\": \"^6.3\",\n \"spatie/laravel-ignition\": \"^2.9\",\n \"spatie/laravel-webhook-server\": \"^3.8\",\n \"staudenmeir/belongs-to-through\": \"^2.17\",\n \"stevenmaguire/oauth2-salesforce\": \"^2.0\",\n \"symfony/cache\": \"^7.2\",\n \"symfony/console\": \"^7.2\",\n \"symfony/css-selector\": \"^7.2\",\n \"symfony/debug\": \"^4.4\",\n \"symfony/dom-crawler\": \"^7.2\",\n \"symfony/expression-language\": \"^7.2\",\n \"symfony/finder\": \"^7.2\",\n \"symfony/http-client\": \"^7.3\",\n \"symfony/http-foundation\": \"^7.2\",\n \"symfony/http-kernel\": \"^7.2\",\n \"symfony/postmark-mailer\": \"^7.3\",\n \"symfony/process\": \"^7.3\",\n \"symfony/property-access\": \"^7.2\",\n \"symfony/psr-http-message-bridge\": \"^7.0\",\n \"symfony/var-dumper\": \"^7.2\",\n \"symfony/workflow\": \"^7.2\",\n \"tecnickcom/tcpdf\": \"^6.11\",\n \"thenetworg/oauth2-azure\": \"dev-master\",\n \"tmannherz/oauth2-ringcentral\": \"dev-master\",\n \"twilio/sdk\": \"^8.3\",\n \"vanderlee/php-sentence\": \"^1.0\",\n \"vinkla/hashids\": \"^13.0\",\n \"vlucas/phpdotenv\": \"^5.4\",\n \"wildbit/postmark-php\": \"^6.0\",\n \"willdurand/email-reply-parser\": \"^2.8\",\n \"zbateson/mail-mime-parser\": \"^3.0.4\"\n },\n \"require-dev\": {\n \"barryvdh/laravel-debugbar\": \"^3.15\",\n \"barryvdh/laravel-ide-helper\": \"^3.5\",\n \"brianium/paratest\": \"^7.5\",\n \"browserstack/browserstack-local\": \"^1.1.0\",\n \"filp/whoops\": \"^2.9\",\n \"friendsofphp/php-cs-fixer\": \"^3.66\",\n \"infection/infection\": \"^0.29.14\",\n \"jasonmccreary/laravel-test-assertions\": \"^2.5\",\n \"larastan/larastan\": \"^3.1\",\n \"maglnet/composer-require-checker\": \"^4.8\",\n \"mockery/mockery\": \"^1.6\",\n \"nunomaduro/collision\": \"^8.6\",\n \"phpstan/phpstan\": \"^2.1\",\n \"phpunit/phpunit\": \"^11.5.50\",\n \"symfony/phpunit-bridge\": \"^7.0\",\n \"vimeo/psalm\": \"^6.5.0\"\n },\n \"autoload\": {\n \"classmap\": [\n \"database\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\",\n \"Database\\\\Factories\\\\\": \"database/factories/\",\n \"Database\\\\Seeders\\\\\": \"database/seeders/\",\n \"Microsoft\\\\Graph\\\\Generated\\\\Models\\\\\": \"app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/\"\n },\n \"files\": [\n \"app/helpers.php\"\n ]\n },\n \"autoload-dev\": {\n \"classmap\": [\n \"tests/TestCase.php\"\n ],\n \"psr-4\": {\n \"Jiminny\\\\\": \"app/\",\n \"Tests\\\\\": \"tests/\"\n }\n },\n \"scripts\": {\n \"post-root-package-install\": [\n \"php -r \\\"file_exists('.env') || copy('.env.example', '.env');\\\"\"\n ],\n \"post-create-project-cmd\": [\n \"php artisan key:generate --ansi\"\n ],\n \"post-install-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postInstall\"\n ],\n \"post-update-cmd\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postUpdate\",\n \"php artisan ide-helper:generate\",\n \"php artisan ide-helper:meta\",\n \"@php artisan vendor:publish --tag=laravel-assets --ansi --force\"\n ],\n \"post-autoload-dump\": [\n \"Illuminate\\\\Foundation\\\\ComposerScripts::postAutoloadDump\",\n \"@php artisan package:discover --ansi\"\n ]\n },\n \"config\": {\n \"preferred-install\": \"dist\",\n \"sort-packages\": true,\n \"optimize-autoloader\": true,\n \"allow-plugins\": {\n \"infection/extension-installer\": true,\n \"php-http/discovery\": true,\n \"tbachert/spi\": true\n }\n },\n \"extra\": {\n \"laravel\": {\n \"dont-discover\": [\n \"laravel/dusk\"\n ]\n },\n \"metasyntactical/composer-plugin-license-check\": {\n \"whitelist\": [],\n \"blacklist\": [\n \"AGPL\"\n ]\n }\n },\n \"repositories\": [\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/PHP-FFMpeg/BinaryDriver.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-salesloft.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-aircall.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-pipedrive.git\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-ringcentral\"\n },\n {\n \"type\": \"vcs\",\n \"url\": \"https://github.com/jiminny/oauth2-dialpad.git\"\n }\n ],\n \"prefer-stable\": true\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Install","depth":3,"help_text":"Installs packages from composer.json, taking account of composer.lock","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Update","depth":3,"help_text":"Installs latest appropriate versions of packages from composer.json","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Show log","depth":3,"help_text":"Show log of Composer-related actions","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1671671177896523754
|
8112816735717670812
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
67
1
7
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Jiminny\Component\Model\BitwiseFlagTrait;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\PlaylistRepository;
use Jiminny\Events\Users\GroupChangedEvent;
use Jiminny\Models\Activity\AvailabilityNotification;
use Jiminny\Models\Activity\CoachRequest;
use Jiminny\Models\Activity\Moment;
use Jiminny\Models\Activity\Note;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SubscriptionSet;
use Jiminny\Models\AskAnything\UserAskAnythingPrompt;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Playlist\Share;
use Jiminny\Notifications\Channels\EmailNotifiableInterface;
use Jiminny\Services\UserService;
use Jiminny\Traits\Enums;
use Jiminny\Traits\RequiresUUID;
use Laratrust\Contracts\LaratrustUser;
use Laratrust\Traits\HasRolesAndPermissions;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
/**
* Jiminny\Models\User
*
* @method string[] getRoles() laratrust's annotation is outright wrong! Also, we don't support using the $team argument.
*
* @property int $id
* @property mixed $uuid
* @property string $name
* @property string $email
* @property string|null $secondary_email
* @property int $status
* @property string|null $password
* @property string|null $remember_token
* @property string|null $photo_path
* @property bool $uses_two_factor_auth
* @property string|null $authy_id
* @property string|null $country_code
* @property int|null $region_id
* @property string|null $phone
* @property string|null $secondary_phone
* @property string|null $caller_id
* @property int|null $job_title_id
* @property string|null $two_factor_reset_code
* @property int $team_id
* @property int|null $group_id
* @property string|null $timezone
* @property string $language
* @property string|null $conference_number
* @property string|null $conference_pin
* @property string|null $conference_slug
* @property string $conference_join_preference
* @property bool $conference_join_reminder
* @property string $conference_record_announce
* @property int $conference_record_preference
* @property int $conference_bandwidth
* @property int $conference_notify_sms
* @property int $conference_start_webcam
* @property int $conference_auto_join_by_computer
* @property int $conference_reduce_video_resolution
* @property string|null $softphone_number
* @property string|null $softphone_inbound_destination
* @property int $softphone_record_preference
* @property int $softphone_passthru_pause
* @property int $softphone_callerid_preference
* @property bool $softphone_debug
* @property int|null $transcription_model_locale_id
* @property string $activity_log_reminder
* @property bool $activity_action_items
* @property bool $slack_follow_up
* @property string $conference_sidekick_open
* @property string $softphone_sidekick_open
* @property bool|null $notify_live_coaching
* @property bool $sync_email
* @property bool $sync_dialer
* @property bool $sync_conference
* @property bool $crm_required
* @property \Illuminate\Support\Carbon|null $nudges_sent_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Collection<int, \Jiminny\Models\Activity> $activities
* @property-read int|null $activities_count
* @property-read Collection<int, AvailabilityNotification> $availabilityNotifications
* @property-read int|null $availability_notifications_count
* @property-read Collection<int, \Jiminny\Models\Calendar> $calendars
* @property-read int|null $calendars_count
* @property-read Collection<int, \Laravel\Passport\Client> $clients
* @property-read int|null $clients_count
* @property-read Collection<int, CoachRequest> $coachRequests
* @property-read int|null $coach_requests_count
* @property-read Profile|null $crmProfile
* @property-read Collection<int, \Jiminny\Models\Device> $devices
* @property-read int|null $devices_count
* @property-read mixed $first_name
* @property-read null|string $formatted_softphone_number
* @property-read string $id_string
* @property-read string $language_with_hyphen
* @property-read string $photo_url
* @property-read \Jiminny\Models\Group|null $group
* @property-read \Jiminny\Models\Inbox|null $inbox
* @property-read Collection<int, \Jiminny\Models\Invitation> $invitations
* @property-read int|null $invitations_count
* @property-read \Jiminny\Models\JobTitle|null $job
* @property-read Collection<int, Moment> $moments
* @property-read int|null $moments_count
* @property-read Collection<int, Note> $notes
* @property-read int|null $notes_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read Collection<int, \Jiminny\Models\Nudge> $nudges
* @property-read int|null $nudges_count
* @property-read Collection<int, \Jiminny\Models\Participant> $participants
* @property-read int|null $participants_count
* @property-read Collection<int, \Jiminny\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read Collection<int, \Jiminny\Models\Playlist\Activity> $playlistActivities
* @property-read int|null $playlist_activities_count
* @property-read Collection<int, Share> $playlistSharesFrom
* @property-read int|null $playlist_shares_from_count
* @property-read Collection<int, Share> $playlistSharesTo
* @property-read int|null $playlist_shares_to_count
* @property-read \Kalnoy\Nestedset\Collection<int, \Jiminny\Models\Playlist> $playlists
* @property-read int|null $playlists_count
* @property-read \Jiminny\Models\Region|null $region
* @property-read Collection<int, \Jiminny\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read Collection<int, Search> $searches
* @property-read int|null $searches_count
* @property-read Collection<int, \Jiminny\Models\SocialAccount> $socialAccounts
* @property-read int|null $social_accounts_count
* @property-read \Jiminny\Models\PhoneNumber|null $softphoneNumberCapabilities
* @property-read Collection<int, \Jiminny\Models\Activity\Subscription> $subscribers
* @property-read int|null $subscribers_count
* @property-read Collection<int, SubscriptionSet> $subscriptionSets
* @property-read int|null $subscription_sets_count
* @property-read \Jiminny\Models\Team $team
* @property-read Collection<int, \Laravel\Passport\Token> $tokens
* @property-read int|null $tokens_count
* @property-read \Jiminny\Models\TranscriptionModelLocale|null $transcriptionModelLocale
* @property-read DatabaseNotificationCollection $unreadNotifications
*
* @method static Builder|User active()
* @method static \Database\Factories\UserFactory factory(...$parameters)
* @method static Builder|User idOrUuId($idOrUuid, bool $first = true)
* @method static Builder|User newModelQuery()
* @method static Builder|User newQuery()
* @method static Builder|User orWhereHasPermission($permission = '')
* @method static Builder|User orWhereHasRole($role = '', $team = null)
* @method static Builder|User query()
* @method static Builder|User shouldSyncDialers()
* @method static Builder|User uuid(string $uuid, bool $first = true)
* @method static Builder|User whereActivityActionItems($value)
* @method static Builder|User whereActivityLogReminder($value)
* @method static Builder|User whereAuthyId($value)
* @method static Builder|User whereCallerId($value)
* @method static Builder|User whereConferenceAutoJoinByComputer($value)
* @method static Builder|User whereConferenceBandwidth($value)
* @method static Builder|User whereConferenceJoinPreference($value)
* @method static Builder|User whereConferenceJoinReminder($value)
* @method static Builder|User whereConferenceNotifySms($value)
* @method static Builder|User whereConferenceNumber($value)
* @method static Builder|User whereConferencePin($value)
* @method static Builder|User whereConferenceRecordAnnounce($value)
* @method static Builder|User whereConferenceRecordPreference($value)
* @method static Builder|User whereConferenceReduceVideoResolution($value)
* @method static Builder|User whereConferenceSidekickOpen($value)
* @method static Builder|User whereConferenceSlug($value)
* @method static Builder|User whereConferenceStartWebcam($value)
* @method static Builder|User whereCountryCode($value)
* @method static Builder|User whereCreatedAt($value)
* @method static Builder|User whereCrmRequired($value)
* @method static Builder|User whereDeletedAt($value)
* @method static Builder|User whereDoesntHavePermission()
* @method static Builder|User whereDoesntHaveRoles()
* @method static Builder|User whereEmail($value)
* @method static Builder|User whereGroupId($value)
* @method static Builder|User whereId($value)
* @method static Builder|User whereJobTitleId($value)
* @method static Builder|User whereLanguage($value)
* @method static Builder|User whereName($value)
* @method static Builder|User whereNotifyLiveCoaching($value)
* @method static Builder|User wherePassword($value)
* @method static Builder|User whereHasPermission($permission = '', $boolean = 'and')
* @method static Builder|User wherePhone($value)
* @method static Builder|User wherePhotoPath($value)
* @method static Builder|User whereRegionId($value)
* @method static Builder|User whereRememberToken($value)
* @method static Builder|User whereHasRole($role = '', $team = null, $boolean = 'and')
* @method static Builder|User whereSecondaryEmail($value)
* @method static Builder|User whereSecondaryPhone($value)
* @method static Builder|User whereSoftphoneCalleridPreference($value)
* @method static Builder|User whereSoftphoneDebug($value)
* @method static Builder|User whereSoftphoneInboundDestination($value)
* @method static Builder|User whereSoftphoneNumber($value)
* @method static Builder|User whereSoftphonePassthruPause($value)
* @method static Builder|User whereSoftphoneRecordPreference($value)
* @method static Builder|User whereSoftphoneSidekickOpen($value)
* @method static Builder|User whereStatus($value)
* @method static Builder|User whereSyncConference($value)
* @method static Builder|User whereSyncDialer($value)
* @method static Builder|User whereSyncEmail($value)
* @method static Builder|User whereTeamId($value)
* @method static Builder|User whereTimezone($value)
* @method static Builder|User whereTranscriptionModelLocaleId($value)
* @method static Builder|User whereTwoFactorResetCode($value)
* @method static Builder|User whereUpdatedAt($value)
* @method static Builder|User whereUsesTwoFactorAuth($value)
* @method static Builder|User whereUuid($value)
* @method static activeForTeam(int $teamId)
*
* @mixin \Eloquent
*/
class User extends AuthenticatableUser implements
EmailNotifiableInterface,
UserContract,
LaratrustUser,
OAuthenticatable
{
use HasFactory;
use HasApiTokens;
use HasRolesAndPermissions;
use RequiresUUID;
use Enums;
use Notifiable;
use BitwiseFlagTrait;
use Authorizable;
public const string ACTIVITY_LOG_REMINDER_OFF = 'off';
public const string ACTIVITY_LOG_REMINDER_SLACK = 'slack';
public const string ACTIVITY_LOG_REMINDER_EMAIL = 'email';
public const int STATUS_ACTIVE = 1;
public const int STATUS_DEACTIVATED = 2;
public const string ROLE_ADMIN = 'admin';
public const string ROLE_MANAGER = 'manager';
public const string ROLE_RECORDER = 'recorder';
public const string ROLE_ANALYST = 'analyst';
public const string ROLE_RECORDER_AND_VOICE = 'recorder_and_voice';
public const string ROLE_LISTENER = 'listener';
public const array MANAGER_ROLES = [
self::ROLE_MANAGER,
self::ROLE_ADMIN,
];
public const int FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED = 2;
public const int FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED = 1;
public const int FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED = 2;
// Log activity to CRM reminder
public array $enumActivityLogReminder = [
self::ACTIVITY_LOG_REMINDER_OFF,
self::ACTIVITY_LOG_REMINDER_SLACK,
self::ACTIVITY_LOG_REMINDER_EMAIL,
];
protected $fillable = [
'name',
'email',
'secondary_email',
'photo_path',
'phone',
'secondary_phone',
'caller_id',
'country_code',
'region_id',
'timezone',
'language',
'conference_slug', // XXX: To be removed.
'conference_record_preference',
'conference_join_reminder',
'softphone_number',
'softphone_inbound_destination',
'softphone_record_preference',
'softphone_passthru_pause',
'softphone_debug',
'team_id',
'group_id',
'status',
'job_title_id',
'transcription_model_locale_id',
'notify_live_coaching',
'activity_log_reminder',
'conference_sidekick_open',
'softphone_sidekick_open',
'activity_action_items',
'slack_follow_up',
'sync_email',
'sync_conference',
'sync_dialer',
'crm_required',
'nudges_sent_at',
];
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'uuid',
'password',
'remember_token',
'authy_id',
'uses_two_factor_auth',
'two_factor_reset_code',
];
protected $appends = [
'id_string',
'formatted_softphone_number',
'photo_url',
];
protected $visible = [
'id_string',
'team',
'group',
'name',
'email',
'role',
'photo_url',
'phone',
'secondary_phone',
'photo_path',
'country_code',
'timezone',
'language',
'conference_slug',
'status',
'softphone_record_preference',
'conference_record_preference',
'job',
];
protected $observables = ['activated', 'deactivated'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'nudges_sent_at' => 'datetime',
'uses_two_factor_auth' => 'boolean',
'conference_record_preference' => 'integer',
'conference_join_reminder' => 'boolean',
'softphone_record_preference' => 'integer',
'softphone_debug' => 'boolean',
'notify_live_coaching' => 'boolean',
'activity_action_items' => 'boolean',
'slack_follow_up' => 'boolean',
'sync_email' => 'boolean',
'sync_conference' => 'boolean',
'sync_dialer' => 'boolean',
'crm_required' => 'boolean',
];
}
public function activate(): void
{
$this->update(['status' => self::STATUS_ACTIVE]);
$this->fireModelEvent('activated', false);
}
public function deactivate(): void
{
$this->update(['status' => self::STATUS_DEACTIVATED]);
$this->fireModelEvent('deactivated', false);
}
public function getIndexableAttributes(?array $loadRelations = null): array
{
$attributes = $this->attributesToArray();
$loadRelationsDefault = [
'team',
'job',
'roles',
'group',
];
$relationsToLoad = is_array($loadRelations) && ! empty($loadRelations)
? $loadRelations
: $loadRelationsDefault;
$relations = [];
if (in_array('team', $relationsToLoad, true)) {
$relations['team'] = $this->getTeam()->getIndexableAttributes();
}
if (in_array('job', $relationsToLoad, true) && $jobTitle = $this->getJobTitle()) {
$relations['job'] = $jobTitle->getIndexableAttributes();
}
if (in_array('roles', $relationsToLoad, true)) {
$relations['roles'] = $this->getAttribute('roles')
->map(static function (Role $role): array {
return $role->getIndexableAttributes();
})
->all();
}
$group = $this->getGroup();
if ($group !== null && in_array('group', $relationsToLoad, true)) {
$relations['group'] = $group->getIndexableAttributes();
}
return array_merge($attributes, $relations);
}
public function shouldSyncDialer(): bool
{
return $this->isStatusActive()
&& $this->hasPermission(PermissionEnum::RECORD_MEETING)
&& $this->sync_dialer;
}
public function shouldSyncCalendarEvents(): bool
{
return $this->hasPermission(PermissionEnum::CALENDAR_SYNC) && $this->sync_conference;
}
/**
* The channels the user receives notification broadcasts on.
*/
public function receivesBroadcastNotificationsOn(): string
{
return 'user.' . $this->id_string;
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(): ?string
{
return $this->team->slackBot->webhook_url;
}
/**
* Always lowercase the slug when we save it to the database.
*/
public function setConferenceSlugAttribute(?string $value): void
{
$this->attributes['conference_slug'] = $value !== null ? strtolower($value) : $value;
}
/**
* Display in national format e.g. [PHONE].
*/
public function getFormattedSoftphoneNumberAttribute(): ?string
{
return phone_national($this->country_code, $this->softphone_number);
}
/**
* Get the url to user photo.
*
* @deprecated - it's depentent on Elastic search
*/
public function getPhotoUrlAttribute(): ?string
{
if (empty($this->photo_path)) {
return null;
}
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $this->photo_path)) {
return $this->photo_path;
}
// Prepend cdn base url to photo uri
return client_cdn($this->photo_path, $this->team);
}
public function getPhotoUrl(): ?string
{
$photoPath = $this->photo_path;
// If user photo_path column in DB is already a fully qualified URL.
if (preg_match('/^https?:\/\//i', $photoPath)) {
return $photoPath;
}
// Prepend cdn base url to photo uri
return $photoPath ? client_cdn($photoPath, $this->team) : null;
}
/**
* Get the activity log to CRM reminder setting, deferring to team for control.
*/
public function getActivityLogReminderAttribute($value): string
{
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
switch ($this->team->activity_log_reminder) {
case Team::ACTIVITY_LOG_REMINDER_OFF:
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_OFF;
break;
case Team::ACTIVITY_LOG_REMINDER_ON:
// When organization setting is set to ON the user can't set it to OFF
if (in_array($value, [self::ACTIVITY_LOG_REMINDER_EMAIL, self::ACTIVITY_LOG_REMINDER_SLACK])) {
$activityLogReminder = $value;
} else {
$activityLogReminder = self::ACTIVITY_LOG_REMINDER_EMAIL;
}
break;
case Team::ACTIVITY_LOG_REMINDER_DELEGATE:
$activityLogReminder = $value;
break;
}
return $activityLogReminder;
}
public function getLanguageWithHyphenAttribute(): string
{
return str_replace('_', '-', $this->language);
}
/**
* Rooms can be accessed from our parent domain.
*/
public function getFirstNameAttribute()
{
return fullNameToFirstName($this->name);
}
public function getFirstName(): string
{
return $this->getAttribute('first_name');
}
public function getTimezone(): \DateTimeZone
{
try {
return new \DateTimeZone($this->getAttribute('timezone'));
} catch (\Exception $e) {
// Get from their team instead.
return $this->getTeam()->getTimezone();
}
}
public function getTimezoneOffset(): string
{
return now()->setTimezone($this->getTimezone())->format('P');
}
public function getLocalTime(?CarbonImmutable $dateTime = null): CarbonImmutable
{
if ($dateTime !== null) {
return $dateTime->setTimezone($this->getTimezone());
}
return CarbonImmutable::now($this->getTimezone());
}
public function getSocialAccount(string $provider): ?SocialAccount
{
/** @var SocialAccount|null */
return $this->socialAccounts()
->where('provider', $provider)
->first();
}
public function hasSocialAccount(string $providerName): bool
{
return $this->socialAccounts()
->where('provider', $providerName)
->exists();
}
public function getStatus(): int
{
return $this->getAttribute('status');
}
public function isStatus(int $status): bool
{
return $this->getStatus() === $status;
}
public function isStatusActive(): bool
{
return $this->isStatus(self::STATUS_ACTIVE);
}
public function isOrganizer(Activity $activity): bool
{
return $this->id && $this->id === $activity->id;
}
/** @return HasOne<Profile> */
public function crmProfile(): HasOne
{
return $this->hasOne(Profile::class);
}
public function job()
{
return $this->hasOne(JobTitle::class, 'id', 'job_title_id');
}
public function devices()
{
return $this->hasMany(Device::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Region
*/
public function region()
{
return $this->belongsTo(Region::class);
}
/**
* Get all of the user's followers.
*/
public function subscribers(): MorphMany
{
return $this->morphMany(Activity\Subscription::class, 'followable');
}
/** @return MorphMany<RoleChangeEvent> */
public function roleChangeEvents(): MorphMany
{
return $this->morphMany(RoleChangeEvent::class, 'affected');
}
/**
* Get all of their subscriptions sets.
*/
public function subscriptionSets()
{
return $this->hasMany(SubscriptionSet::class);
}
/**
* @return HasMany<SocialAccount>
*/
public function socialAccounts(): HasMany
{
/** @var HasMany<SocialAccount> */
return $this->hasMany(SocialAccount::class, 'sociable_id');
}
/**
* Get all of the pending invitations for the user.
*/
public function invitations()
{
return $this->hasMany(Invitation::class);
}
public function calendars(): HasMany
{
return $this->hasMany(Calendar::class);
}
public function inbox(): HasOne
{
return $this->hasOne(Inbox::class);
}
public function moments(): HasMany
{
return $this->hasMany(Moment::class);
}
public function activities(): HasMany
{
return $this->hasMany(Activity::class);
}
public function notes()
{
return $this->hasMany(Note::class);
}
public function participants()
{
return $this->hasMany(Participant::class);
}
public function coachRequests()
{
return $this->hasMany(CoachRequest::class);
}
public function availabilityNotifications()
{
return $this->hasMany(AvailabilityNotification::class);
}
/** @return BelongsTo<Group, self> */
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/** @return BelongsTo<Team, self> */
public function team(): BelongsTo
{
return $this->belongsTo(Team::class);
}
public function playlists()
{
return $this->hasMany(Playlist::class, 'owner_id');
}
public function playlistActivities()
{
return $this->hasMany(Playlist\Activity::class);
}
/**
* Playlist shares from this user.
*/
public function playlistSharesFrom(): HasMany
{
return $this->hasMany(Share::class, 'from_user_id');
}
/**
* Playlist shares to this user.
*/
public function playlistSharesTo(): HasMany
{
return $this->hasMany(Share::class, 'to_user_id');
}
/**
* Returns user's favorite playlist. Creates one if it doesn't exist.
*/
public function favoritePlaylist(): Playlist
{
return app(PlaylistRepository::class)->getDefaultPlaylist($this);
}
#[Scope]
protected function active($query)
{
return $query->where('status', self::STATUS_ACTIVE);
}
#[Scope]
protected function admin($query)
{
return $query->whereHas('roles', static function (Builder $query): void {
$query->whereIn('roles.name', [
User::ROLE_ADMIN,
]);
});
}
#[Scope]
protected function shouldSyncDialers($query)
{
return $query
->whereHasPermission([PermissionEnum::RECORD_MEETING])
->where('status', self::STATUS_ACTIVE)
->where('sync_dialer', true);
}
#[Scope]
protected function activeForTeam($query, int $teamId)
{
return $query->where('status', self::STATUS_ACTIVE)
->where('team_id', $teamId);
}
public function transcriptionModelLocale(): BelongsTo
{
return $this->belongsTo(TranscriptionModelLocale::class);
}
public function searches(): HasMany
{
return $this->hasMany(Search::class);
}
public function nudges(): HasManyThrough
{
return $this->hasManyThrough(
Nudge::class,
Search::class,
'user_id',
'activity_search_id'
);
}
public function askAnythingPrompts(): HasMany
{
return $this->hasMany(UserAskAnythingPrompt::class);
}
/**
* Determine if the user is on the given team.
*/
public function onTeam(Team $team): bool
{
return $this->team_id === $team->id;
}
/**
* Determine if the given team is owned by the user.
*/
public function ownsTeam(?Team $team = null): bool
{
return $this->getId() === ($team ?? $this->getTeam())->owner_id;
}
/**
* Determine if the user is in the given group.
*/
public function inGroup(Group $group): bool
{
return $this->group && $this->group_id === $group->id;
}
/**
* Determine if the given group is owned by the user.
*/
public function ownsGroup(Group $group): bool
{
return $this->id && $group->user_id && $this->id === $group->user_id;
}
/**
* Switch the current group for the user.
*/
public function switchToGroup(Group $group)
{
if ($this->inGroup($group)) {
throw new \InvalidArgumentException('The user is already in the given group.');
}
$this->update(['group_id' => $group->id]);
event(new GroupChangedEvent($this));
}
public function generateHash(): string
{
$hash = hash_hmac(
'sha256',
$this->id_string,
config('app.key')
);
return $hash;
}
public function isHashValid(string $hash): bool
{
return $hash === $this->generateHash();
}
/**
* @deprecated Use Activity::findParticipant() instead.
*/
public function getParticipantByActivity(Activity $activity): Participant
{
return $activity->getParticipant($this);
}
public function softphoneNumberCapabilities(): HasOne
{
return $this->hasOne(PhoneNumber::class, 'number', 'softphone_number');
}
public function hasSoftphoneNumberCapabilities(): bool
{
return $this->getAttribute('softphoneNumberCapabilities') !== null;
}
public function getSoftphoneNumberCapabilities(): PhoneNumber
{
return $this->getAttribute('softphoneNumberCapabilities');
}
public function checkConferenceRecordPreference(): bool
{
$team = $this->team;
return (
$team->isConferenceRecordPreferenceEnabled() ||
($team->isConferenceRecordPreferenceDelegate() && $this->isConferenceRecordPreferenceEnabled())
);
}
public function getConferenceRecordPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED);
}
public function setConferenceRecordPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordPreferenceEnabled(): bool
{
return $this->getConferenceRecordPreference();
}
public function checkConferenceRecordInternalPreference(): bool
{
$team = $this->team;
if ($team->isConferenceRecordPreferenceEnabled()) {
return $team->isConferenceRecordInternalPreferenceEnabled();
}
return $this->isConferenceRecordInternalPreferenceEnabled();
}
private function getConferenceRecordInternalPreference(): bool
{
return $this->getFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED);
}
public function setConferenceRecordInternalPreference(bool $isEnabled): self
{
return $this->setFlag('conference_record_preference', self::FLAG_CONFERENCE_RECORD_INTERNAL_PREFERENCE_ENABLED, $isEnabled);
}
public function isConferenceRecordInternalPreferenceEnabled(): bool
{
$team = $this->getTeam();
return $team->isConferenceRecordPreferenceDelegate() && $this->getConferenceRecordInternalPreference();
}
/**
* Check if should record softphone
*/
public function checkSoftphoneOutboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneOutboundRecordPreferenceEnabled() ||
($team->isSoftphoneOutboundRecordPreferenceDelegate() && $this->isSoftphoneOutboundRecordPreferenceEnabled())
);
}
public function getSoftphoneOutboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneOutboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_OUTBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneOutboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneOutboundRecordPreference();
}
public function checkSoftphoneInboundRecordPreference(): bool
{
$team = $this->team;
return (
$team->isSoftphoneInboundRecordPreferenceEnabled() ||
($team->isSoftphoneInboundRecordPreferenceDelegate() && $this->isSoftphoneInboundRecordPreferenceEnabled())
);
}
public function getSoftphoneInboundRecordPreference(): bool
{
return $this->getFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED);
}
public function setSoftphoneInboundRecordPreference(bool $isEnabled): self
{
return $this->setFlag('softphone_record_preference', self::FLAG_SOFTPHONE_INBOUND_RECORD_PREFERENCE_ENABLED, $isEnabled);
}
public function isSoftphoneInboundRecordPreferenceEnabled(): bool
{
return $this->getSoftphoneInboundRecordPreference();
}
/**
* GETTERS AND SETTERS FOLLOW BELOW THIS LINE
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/** @deprecated use getUuid */
public function getIdString(): string
{
return $this->getAttribute('id_string');
}
public function getName(): string
{
return $this->getAttribute('name');
}
public function setName(string $name): self
{
$this->setAttribute('name', $name);
return $this;
}
public function getEmailAddress(): string
{
return $this->getAttribute('email');
}
public function setEmailAddress(string $emailAddress): self
{
$this->setAttribute('email', $emailAddress);
return $this;
}
public function getSecondaryEmailAddress(): ?string
{
return $this->getAttribute('secondary_email');
}
public function setSecondaryEmailAddress(string $emailAddress): self
{
$this->setAttribute('secondary_email', $emailAddress);
return $this;
}
public function getTranscriptionModelLocale(): TranscriptionModelLocale
{
return $this->getAttribute('transcriptionModelLocale');
}
public function hasTranscriptionModelLocale(): bool
{
return $this->getAttribute('transcriptionModelLocale') !== null;
}
public function getGroup(): ?Group
{
/** @var Group|null */
return $this->getAttribute('group');
}
public function getGroupId(): ?int
{
return $this->getAttribute('group_id');
}
public function hasGroupId(): bool
{
return $this->getAttribute('group_id') !== null;
}
public function isSameGroupId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $authenticatable->hasGroupId() && $this->hasGroupId()
&& $this->getGroupId() === $authenticatable->getGroupId();
}
public function getTeamId(): int
{
return $this->getAttribute('team_id');
}
public function isSameTeamId(Authenticatable $authenticatable): bool
{
return $authenticatable instanceof self
&& $this->getTeamId() === $authenticatable->getTeamId();
}
public function getTeam(): Team
{
return $this->getAttribute('team');
}
public function hasTeam(): bool
{
return $this->getAttribute('team') !== null;
}
public function getJobTitle(): ?JobTitle
{
/** @var JobTitle|null */
return $this->job;
}
public function getCountryCode(): ?string
{
return $this->getAttribute('country_code');
}
public function isSame(self $user): bool
{
return $this->getId() === $user->getId();
}
public function getLanguage(): string
{
return $this->getAttribute('language');
}
public function setLanguage(string $language): self
{
$this->setAttribute('language', $language);
return $this;
}
public function hasCallerId(): bool
{
return $this->getAttribute('caller_id') !== null;
}
public function getCallerId(): ?string
{
return $this->getAttribute('caller_id');
}
public function getSoftPhoneNumber(): ?string
{
/** @var string|null */
return $this->getAttribute('softphone_number');
}
public function getPhone(): ?string
{
return $this->phone;
}
public function getSecondaryPhone(): ?string
{
/** @var string|null */
return $this->getAttribute('secondary_phone');
}
public function hasSoftPhoneInboundDestinationNumber(): bool
{
return $this->getAttribute('softphone_inbound_destination') !== null;
}
public function getSoftPhoneInboundDestinationNumber(): string
{
return $this->getAttribute('softphone_inbound_destination');
}
public function setSoftPhoneInboundDestinationNumber(?string $phoneNumber): self
{
return $this->setAttribute('softphone_inbound_destination', $phoneNumber);
}
public function getRegion(): Region
{
return $this->getAttribute('region');
}
public function hasRegion(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function hasRegionId(): bool
{
return $this->getAttribute('region_id') !== null;
}
public function getRegionId(): ?int
{
return $this->getAttribute('region_id');
}
public function setRegionId(?int $regionId): self
{
$this->setAttribute('region_id', $regionId);
return $this;
}
public function getSidekickLaunchSettingsForMeeting(): string
{
return $this->getConferenceSidekickOpen();
}
public function getSidekickLaunchSettingsForCall(): string
{
return $this->getSoftphoneSidekickOpen();
}
public function getConferenceSidekickOpen(): ?string
{
return $this->getAttribute('conference_sidekick_open');
}
public function getSoftphoneSidekickOpen(): ?string
{
return $this->getAttribute('softphone_sidekick_open');
}
public function isActivityActionItemsEnabled(): bool
{
return $this->getAttribute('activity_action_items');
}
public function isSlackFollowUpEnabled(): bool
{
return $this->getAttribute('slack_follow_up');
}
public function disableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', false);
return $this;
}
public function enableSlackFollowUp(): self
{
$this->setAttribute('slack_follow_up', true);
return $this;
}
public function getRecipientEmail(): string
{
return $this->getEmailAddress();
}
public function getTeamContext(): ?Team
{
return $this->hasTeam() ? $this->getTeam() : null;
}
public function getInbox(): ?Inbox
{
return $this->getAttribute('inbox');
}
public function isSyncEmailEnabled(): bool
{
return $this->getAttribute('sync_email') === true;
}
public function isCrmRequired(): bool
{
return $this->getAttribute('crm_required');
}
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
public function getProfile(): ?Profile
{
/** @var Profile */
return $this->getAttribute('crmProfile');
}
public function isTeamOwner(): bool
{
return $this->getId() === $this->getTeam()->owner_id;
}
public function isAdmin(): bool
{
return $this->hasRole(self::ROLE_ADMIN);
}
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
public function getNudgesSentAt(): ?Carbon
{
return $this->getAttribute('nudges_sent_at');
}
public function languageDialects(): BelongsToMany
{
return $this->belongsToMany(LanguageDialect::class, 'user_languages', 'user_id', 'language_dialect_id')
->withTimestamps();
}
/**
* @return Collection<int, LanguageDialect>
*/
public function getLanguageDialects(): Collection
{
return $this->getAttribute('languageDialects');
}
public function getDateTimeFormat(?string $format = null): string
{
return app(UserService::class)->getDateFormat($this, $format);
}
public function getConferenceSlug(): ?string
{
return $this->getAttribute('conference_slug');
}
public function isSyncConferenceEnabled(): bool
{
return $this->getAttribute('sync_conference');
}
public function getPhotoPath(): ?string
{
return $this->getAttribute('photo_path');
}
public function setPhotoPath(?string $path): self
{
$this->setAttribute('photo_path', $path);
return $this;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
2
14
Previous Highlighted Error
Next Highlighted Error
{
"name": "jiminny/app",
"description": "The Jiminny Platform.",
"keywords": [
"training",
"salesforce",
"conference"
],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-iconv": "*",
"ext-igbinary": "*",
"ext-imagick": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mailparse": "*",
"ext-mbstring": "*",
"ext-mysqlnd": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-pcre": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-phar": "*",
"ext-phpiredis": "*",
"ext-posix": "*",
"ext-readline": "*",
"ext-redis": "*",
"ext-reflection": "*",
"ext-session": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-zlib": "*",
"lib-curl": "*",
"lib-curl-openssl": "*",
"lib-curl-zlib": "*",
"lib-date-timelib": "*",
"lib-date-zoneinfo": "*",
"lib-fileinfo-libmagic": "*",
"lib-gd": "*",
"lib-gd-freetype": "*",
"lib-gd-libjpeg": "*",
"lib-gd-libpng": "*",
"lib-gmp": "*",
"lib-icu": "*",
"lib-icu-cldr": "*",
"lib-icu-unicode": "*",
"lib-imagick-imagemagick": "*",
"lib-libxml": "*",
"lib-mbstring-libmbfl": "*",
"lib-mbstring-oniguruma": "*",
"lib-openssl": "*",
"lib-pcre": "*",
"lib-pcre-unicode": "*",
"lib-zip-libzip": "*",
"lib-zlib": "*",
"24slides/laravel-saml2": "^2.4",
"adam-paterson/oauth2-slack": "^1.1",
"asimlqt/php-google-spreadsheet-client": "^3.0",
"aws/aws-sdk-php": "^3.368",
"aws/aws-sdk-php-laravel": "^3.10",
"bepsvpt/secure-headers": "^9.0",
"chadhutchins/oauth2-slack": "^1.2",
"chaseconey/laravel-datadog-helper": "^1.2",
"chrisyue/php-m3u8": "4.0.3",
"daniti/oauth2-pipedrive": "dev-master",
"devio/pipedrive": "^2.6",
"doctrine/dbal": "^4.0",
"elasticsearch/elasticsearch": "^7.11",
"erusev/parsedown": "^1.7",
"fakerphp/faker": "^1.23",
"firebase/php-jwt": "^7.0",
"flipboxdigital/oauth2-hubspot": "1.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.360",
"google/apps-meet": "^0.5.1",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.6",
"halaxa/json-machine": "^1.2",
"html2text/html2text": "^4.3",
"hubspot/api-client": "~5.0.0",
"hubspot/hubspot-php": "^5.2.0",
"intercom/intercom-php": "^4.5",
"intervention/image": "^3.4",
"jakeasmith/http_build_url": "^1.0",
"jdavidbakr/cloudfront-proxies": "^1.7",
"jeremykendall/php-domain-parser": "^6.3",
"jiminny/oauth2-aircall": "dev-master",
"jiminny/oauth2-bullhorn": "^0.2.0",
"jiminny/oauth2-dialpad": "dev-master",
"jiminny/oauth2-salesloft": "dev-master",
"jolicode/slack-php-api": "^4.5.0",
"kalnoy/nestedset": "*",
"laravel/framework": "^12.28",
"laravel/helpers": "^1.7",
"laravel/passport": "^13.0",
"laravel/slack-notification-channel": "^3.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"laravolt/avatar": "^6.1",
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "*",
"league/oauth2-client": "^2.7",
"league/oauth2-google": "^4.0",
"league/oauth2-linkedin": "^5.1",
"league/oauth2-server": "^9.2",
"league/statsd": "^2.0",
"markrogoyski/math-php": "^2.7.0",
"microsoft/microsoft-graph": "^2.51",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8",
"nette/caching": "*",
"phlib/sms-length": "^2.0",
"php-ffmpeg/php-ffmpeg": "^1.2",
"php-http/client-common": "^2.7",
"php-http/curl-client": "^2.3",
"php-http/httplug": "^2.2",
"php-http/message": "^1.16",
"phpseclib/phpseclib": "^3.0.36",
"propaganistas/laravel-phone": "^5.3",
"psr/cache": "^3.0",
"psr/http-message": "^2.0",
"psr/log": "^3.0",
"psr/simple-cache": "^3.0",
"pusher/pusher-php-server": "7.2.3",
"ramsey/uuid": "^4.2",
"ringcentral/ringcentral-php": "3.0.0",
"rmccue/requests": "^2.0",
"ruflin/elastica": "^7.1.1",
"santigarcor/laratrust": "^8.4",
"sentry/sentry": "4.13.0",
"sentry/sentry-laravel": "~4.13.0",
"shiftonelabs/laravel-sqs-fifo-queue": "^3.0",
"spatie/fractalistic": "^2.9",
"spatie/laravel-fractal": "^6.3",
"spatie/laravel-ignition": "^2.9",
"spatie/laravel-webhook-server": "^3.8",
"staudenmeir/belongs-to-through": "^2.17",
"stevenmaguire/oauth2-salesforce": "^2.0",
"symfony/cache": "^7.2",
"symfony/console": "^7.2",
"symfony/css-selector": "^7.2",
"symfony/debug": "^4.4",
"symfony/dom-crawler": "^7.2",
"symfony/expression-language": "^7.2",
"symfony/finder": "^7.2",
"symfony/http-client": "^7.3",
"symfony/http-foundation": "^7.2",
"symfony/http-kernel": "^7.2",
"symfony/postmark-mailer": "^7.3",
"symfony/process": "^7.3",
"symfony/property-access": "^7.2",
"symfony/psr-http-message-bridge": "^7.0",
"symfony/var-dumper": "^7.2",
"symfony/workflow": "^7.2",
"tecnickcom/tcpdf": "^6.11",
"thenetworg/oauth2-azure": "dev-master",
"tmannherz/oauth2-ringcentral": "dev-master",
"twilio/sdk": "^8.3",
"vanderlee/php-sentence": "^1.0",
"vinkla/hashids": "^13.0",
"vlucas/phpdotenv": "^5.4",
"wildbit/postmark-php": "^6.0",
"willdurand/email-reply-parser": "^2.8",
"zbateson/mail-mime-parser": "^3.0.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
"barryvdh/laravel-ide-helper": "^3.5",
"brianium/paratest": "^7.5",
"browserstack/browserstack-local": "^1.1.0",
"filp/whoops": "^2.9",
"friendsofphp/php-cs-fixer": "^3.66",
"infection/infection": "^0.29.14",
"jasonmccreary/laravel-test-assertions": "^2.5",
"larastan/larastan": "^3.1",
"maglnet/composer-require-checker": "^4.8",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5.50",
"symfony/phpunit-bridge": "^7.0",
"vimeo/psalm": "^6.5.0"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Microsoft\\Graph\\Generated\\Models\\": "app/Services/MeetingGenerator/Overrides/Microsoft/Graph/Generated/Models/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
],
"psr-4": {
"Jiminny\\": "app/",
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate --ansi"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall"
...
|
NULL
|
|
61466
|
1327
|
20
|
2026-04-21T06:59:04.643565+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776754744643_m2.jpg...
|
Firefox
|
Service-Desk - Queues - Platform team - Service sp Service-Desk - Queues - Platform team - Service space - Jira — Work...
|
True
|
jiminny.atlassian.net/jira/servicedesk/projects/SR jiminny.atlassian.net/jira/servicedesk/projects/SRD/queues/custom/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
New Tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Queue
Queue
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
3 Notifications
3 Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
Queues
Queues
Create
Create
More for queues
More for queues
Team Priority
Team Priority
All open tickets
All open tickets
Star All open tickets
12
Unassigned tickets
Unassigned tickets
Star Unassigned tickets
2
Support team Queue
Support team Queue
Star Support team Queue
4
Raised by me
Raised by me
Star Raised by me
0
Assigned to me
Assigned to me
Star Assigned to me
1
Service requests
Service requests
Star Service requests
4
Platform team
Platform team
Star Platform team
1
Processing team
Processing team
Star Processing team
9
Site reliability team
Site reliability team
Star Site reliability team
0
New features requests
New features requests
Star New features requests
0
InfoSec issues
InfoSec issues
Star InfoSec issues
0
Ready for Customer
Ready for Customer
Star Ready for Customer
0
Resolved tickets
Resolved tickets
Star Resolved tickets
999+
View all queues
View all queues
Service requests
Service requests
Create
Create
More for service requests
More for service requests...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.09524601,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny MCP Connector - Product - Confluence","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny MCP Connector - Product - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.08294548,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.15791224,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.02144282,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.08610372,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.042719416,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.013131649,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.1740359,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.039228722,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42218676,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.43335995,"width":0.014960106,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.45490822,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.4660814,"width":0.10721409,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.46209097,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.48922586,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.090259306,"top":0.07861133,"width":0.016954787,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Queue","depth":10,"bounds":{"left":0.090259306,"top":0.15522745,"width":0.014461436,"height":0.01396648},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Queue","depth":11,"bounds":{"left":0.090259306,"top":0.15522745,"width":0.014461436,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.08361037,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.0887633,"top":0.06344773,"width":0.039727394,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.095578454,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.10073138,"top":0.06344773,"width":0.044215426,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.10887633,"top":0.057861134,"width":0.029421542,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.40475398,"top":0.06264964,"width":0.24268617,"height":0.015961692},"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.65575135,"top":0.057861134,"width":0.030086435,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.66705453,"top":0.06384677,"width":0.014793883,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.91223407,"top":0.057861134,"width":0.035904255,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.92353725,"top":0.06384677,"width":0.020611702,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"3 Notifications","depth":12,"bounds":{"left":0.9494681,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3 Notifications","depth":14,"bounds":{"left":0.954621,"top":0.06344773,"width":0.031914894,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.96143615,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.9665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9734042,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.97855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.98537236,"top":0.057861134,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.99052525,"top":0.06344773,"width":0.009474754,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.08361037,"top":0.09976058,"width":0.071476065,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.09424867,"top":0.10574621,"width":0.01662234,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Recent","depth":12,"bounds":{"left":0.08361037,"top":0.12529927,"width":0.071476065,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.09424867,"top":0.13128492,"width":0.015458777,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Starred","depth":12,"bounds":{"left":0.08361037,"top":0.15083799,"width":0.071476065,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.09424867,"top":0.15682362,"width":0.016456118,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.08361037,"top":0.1763767,"width":0.071476065,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.09424867,"top":0.18236233,"width":0.011635638,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.15309176,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.08361037,"top":0.2019154,"width":0.071476065,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.09424867,"top":0.20790103,"width":0.016456118,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.13646941,"top":0.20510775,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.14577793,"top":0.20510775,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.08959442,"top":0.23423783,"width":0.013464096,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.08759973,"top":0.2529928,"width":0.0674867,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.09823803,"top":0.25897846,"width":0.032081116,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.08892952,"top":0.25618514,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.15508644,"top":0.25618514,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.16240026,"top":0.25618514,"width":0.0039893617,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"bounds":{"left":0.08759973,"top":0.27853152,"width":0.0674867,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Service-Desk","depth":20,"bounds":{"left":0.09823803,"top":0.28451717,"width":0.03025266,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"bounds":{"left":0.14577793,"top":0.28172386,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Queues","depth":21,"bounds":{"left":0.09158909,"top":0.30407023,"width":0.06349734,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Queues","depth":24,"bounds":{"left":0.1022274,"top":0.31005585,"width":0.017121011,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Create","depth":22,"bounds":{"left":0.13646941,"top":0.30726257,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More for queues","depth":22,"bounds":{"left":0.14577793,"top":0.30726257,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More for queues","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Team Priority","depth":23,"bounds":{"left":0.095578454,"top":0.32960895,"width":0.059507977,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Team Priority","depth":26,"bounds":{"left":0.10621676,"top":0.33559456,"width":0.029587766,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"All open tickets","depth":25,"bounds":{"left":0.099567816,"top":0.35514766,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All open tickets","depth":28,"bounds":{"left":0.11020612,"top":0.36113328,"width":0.034075797,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star All open tickets","depth":26,"bounds":{"left":0.14577793,"top":0.35834,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"12","depth":28,"bounds":{"left":0.14777261,"top":0.36272946,"width":0.0039893617,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unassigned tickets","depth":25,"bounds":{"left":0.099567816,"top":0.38068634,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unassigned tickets","depth":28,"bounds":{"left":0.11020612,"top":0.386672,"width":0.03307846,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Unassigned tickets","depth":26,"bounds":{"left":0.14577793,"top":0.38387868,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2","depth":28,"bounds":{"left":0.14860372,"top":0.38826814,"width":0.0023271276,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Support team Queue","depth":25,"bounds":{"left":0.099567816,"top":0.40622506,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Support team Queue","depth":28,"bounds":{"left":0.11020612,"top":0.4122107,"width":0.03025266,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Support team Queue","depth":26,"bounds":{"left":0.14577793,"top":0.4094174,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4","depth":28,"bounds":{"left":0.1484375,"top":0.41380686,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Raised by me","depth":25,"bounds":{"left":0.099567816,"top":0.43176377,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Raised by me","depth":28,"bounds":{"left":0.11020612,"top":0.43774942,"width":0.029753989,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Raised by me","depth":26,"bounds":{"left":0.14577793,"top":0.4349561,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":28,"bounds":{"left":0.1484375,"top":0.43934557,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Assigned to me","depth":25,"bounds":{"left":0.099567816,"top":0.45730248,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Assigned to me","depth":28,"bounds":{"left":0.11020612,"top":0.4632881,"width":0.03174867,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Assigned to me","depth":26,"bounds":{"left":0.14577793,"top":0.46049482,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":28,"bounds":{"left":0.14893617,"top":0.46488428,"width":0.0016622341,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Service requests","depth":25,"bounds":{"left":0.099567816,"top":0.4828412,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service requests","depth":28,"bounds":{"left":0.11020612,"top":0.4888268,"width":0.033410903,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Service requests","depth":26,"bounds":{"left":0.14577793,"top":0.48603353,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4","depth":28,"bounds":{"left":0.1484375,"top":0.490423,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform team","depth":25,"bounds":{"left":0.099567816,"top":0.5083799,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform team","depth":28,"bounds":{"left":0.11020612,"top":0.5143655,"width":0.031083776,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Platform team","depth":26,"bounds":{"left":0.14577793,"top":0.51157224,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":28,"bounds":{"left":0.14893617,"top":0.5159617,"width":0.0016622341,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing team","depth":25,"bounds":{"left":0.099567816,"top":0.5339186,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing team","depth":28,"bounds":{"left":0.11020612,"top":0.53990424,"width":0.03307846,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Processing team","depth":26,"bounds":{"left":0.14577793,"top":0.5371109,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"9","depth":28,"bounds":{"left":0.14860372,"top":0.5415004,"width":0.0023271276,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Site reliability team","depth":25,"bounds":{"left":0.099567816,"top":0.5594573,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Site reliability team","depth":28,"bounds":{"left":0.11020612,"top":0.5654429,"width":0.03274601,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Site reliability team","depth":26,"bounds":{"left":0.14577793,"top":0.56264967,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":28,"bounds":{"left":0.1484375,"top":0.56703913,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"New features requests","depth":25,"bounds":{"left":0.099567816,"top":0.584996,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New features requests","depth":28,"bounds":{"left":0.11020612,"top":0.59098166,"width":0.032579787,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star New features requests","depth":26,"bounds":{"left":0.14577793,"top":0.58818835,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":28,"bounds":{"left":0.1484375,"top":0.5925778,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"InfoSec issues","depth":25,"bounds":{"left":0.099567816,"top":0.6105347,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"InfoSec issues","depth":28,"bounds":{"left":0.11020612,"top":0.61652035,"width":0.03324468,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star InfoSec issues","depth":26,"bounds":{"left":0.14577793,"top":0.61372703,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":28,"bounds":{"left":0.1484375,"top":0.6181165,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Ready for Customer","depth":25,"bounds":{"left":0.099567816,"top":0.6360734,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready for Customer","depth":28,"bounds":{"left":0.11020612,"top":0.6420591,"width":0.032912236,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Ready for Customer","depth":26,"bounds":{"left":0.14577793,"top":0.6392658,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":28,"bounds":{"left":0.1484375,"top":0.64365524,"width":0.0026595744,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Resolved tickets","depth":25,"bounds":{"left":0.099567816,"top":0.66161215,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Resolved tickets","depth":28,"bounds":{"left":0.11020612,"top":0.6675978,"width":0.027094414,"height":0.029928172},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star Resolved tickets","depth":26,"bounds":{"left":0.14112367,"top":0.66480446,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"999+","depth":28,"bounds":{"left":0.14245346,"top":0.6691939,"width":0.009973404,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"View all queues","depth":23,"bounds":{"left":0.095578454,"top":0.68715084,"width":0.059507977,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View all queues","depth":26,"bounds":{"left":0.10621676,"top":0.69313645,"width":0.034906916,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service requests","depth":21,"bounds":{"left":0.09158909,"top":0.7126895,"width":0.06349734,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Service requests","depth":24,"bounds":{"left":0.1022274,"top":0.7186752,"width":0.03756649,"height":0.01396648},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Create","depth":22,"bounds":{"left":0.15309176,"top":0.7158819,"width":0.0039893617,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More for service requests","depth":22,"bounds":{"left":0.15442154,"top":0.7158819,"width":0.0039893617,"height":0.01915403},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More for service requests","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8588753735880809136
|
8111376065084974257
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
New Tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Queue
Queue
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
3 Notifications
3 Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
Queues
Queues
Create
Create
More for queues
More for queues
Team Priority
Team Priority
All open tickets
All open tickets
Star All open tickets
12
Unassigned tickets
Unassigned tickets
Star Unassigned tickets
2
Support team Queue
Support team Queue
Star Support team Queue
4
Raised by me
Raised by me
Star Raised by me
0
Assigned to me
Assigned to me
Star Assigned to me
1
Service requests
Service requests
Star Service requests
4
Platform team
Platform team
Star Platform team
1
Processing team
Processing team
Star Processing team
9
Site reliability team
Site reliability team
Star Site reliability team
0
New features requests
New features requests
Star New features requests
0
InfoSec issues
InfoSec issues
Star InfoSec issues
0
Ready for Customer
Ready for Customer
Star Ready for Customer
0
Resolved tickets
Resolved tickets
Star Resolved tickets
999+
View all queues
View all queues
Service requests
Service requests
Create
Create
More for service requests
More for service requests...
|
NULL
|
|
7517
|
140
|
15
|
2026-04-13T15:58:07.137605+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776095887137_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
http://100.73.206.126:8766
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Inbox - [EMAIL] - Gmail
Shameles DXP4800PLUS-B5F8
Inbox - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N
Mute tab
Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
google maps timeline export - Google Search
google maps timeline export - Google Search
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
TOTAL SPAN
12.6h
11:35 → 00:10
ACTIVE TIME
(WALL CLOCK)
4.6h
BREAKS
3 · 8.0h
SESSIONS — CLICK TO FILTER
S1: 11:35–11:46 (11.1m)
S2: 12:10–13:13 (63.3m)
S3: 15:09–16:37 (87.1m)
S4: 22:16–00:10 (113.9m)
Click a session segment to filter activity to that time window
S2 63m
S3 87m
S4 114m
117m
340m
11:35
13:41
15:46
17:52
19:58
22:04
00:10
Filtered:
⏱ S1 11:35–11:46
×
📱 Firefox
×
FRAMES
2455
APPS
10
UI EVENTS
3628
AUDIO
0
ACTIVE PERIOD
(TIMES IN LOCAL TIMEZONE)
11:35 → 00:10
TIME PER APP
— CLICK TO FILTER ALL PANELS BY APP
iTerm2
0.9m
Claude
0.5m
Firefox
0.3m
Alfred
0.3m
Websites
Windows
UI Events
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909
0.1m
mail.google.com/mail/u/0/#inbox
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37
0m
app.dev.jiminny.com
0m...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Shameless • HBO Max","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shameless • HBO Max","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Mute tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Machines - Tailscale","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Machines - Tailscale","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pazaruvaj.com — Знак, че е време за най-добрите оферти","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"google maps timeline export - Google Search","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"google maps timeline export - Google Search","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Screenpipe","depth":7,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"04","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"TOTAL SPAN","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12.6h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:35 → 00:10","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ACTIVE TIME","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(WALL CLOCK)","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.6h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BREAKS","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3 · 8.0h","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SESSIONS — CLICK TO FILTER","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S1: 11:35–11:46 (11.1m)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S2: 12:10–13:13 (63.3m)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S3: 15:09–16:37 (87.1m)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S4: 22:16–00:10 (113.9m)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Click a session segment to filter activity to that time window","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S2 63m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S3 87m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"S4 114m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"117m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"340m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:35","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13:41","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15:46","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17:52","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19:58","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22:04","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"00:10","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filtered:","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"⏱ S1 11:35–11:46","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"×","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"📱 Firefox","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"×","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FRAMES","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2455","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"APPS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UI EVENTS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3628","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AUDIO","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ACTIVE PERIOD","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(TIMES IN LOCAL TIMEZONE)","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:35 → 00:10","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TIME PER APP","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— CLICK TO FILTER ALL PANELS BY APP","depth":9,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iTerm2","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.9m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.5m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Firefox","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.3m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alfred","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.3m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Websites","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Windows","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"UI Events","depth":8,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mail.google.com/mail/u/0/#inbox","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.1m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny.atlassian.net/jira/software/c/projects/JY/boards/37","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app.dev.jiminny.com","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0m","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
7365218831019936036
|
8109121378649843928
|
click
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Inbox - [EMAIL] - Gmail
Shameles DXP4800PLUS-B5F8
Inbox - [EMAIL] - Gmail
Shameless • HBO Max
Shameless • HBO Max
Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N
Mute tab
Proeurópska bublina si po Orbánovom páde vydýchla, víťazná Tisza však môže Brusel prekvapiť — Denník N
Machines - Tailscale
Machines - Tailscale
Screenpipe Dashboard
Screenpipe Dashboard
Close tab
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Pazaruvaj.com — Знак, че е време за най-добрите оферти
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
Screenpipe: Open Source 24/7 Screen & Audio Capture : r/software
google maps timeline export - Google Search
google maps timeline export - Google Search
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Screenpipe
Screenpipe
Activity
Search
Audio
Work Report
AI Summary
Date
12
/
04
/
2026
Calendar
TOTAL SPAN
12.6h
11:35 → 00:10
ACTIVE TIME
(WALL CLOCK)
4.6h
BREAKS
3 · 8.0h
SESSIONS — CLICK TO FILTER
S1: 11:35–11:46 (11.1m)
S2: 12:10–13:13 (63.3m)
S3: 15:09–16:37 (87.1m)
S4: 22:16–00:10 (113.9m)
Click a session segment to filter activity to that time window
S2 63m
S3 87m
S4 114m
117m
340m
11:35
13:41
15:46
17:52
19:58
22:04
00:10
Filtered:
⏱ S1 11:35–11:46
×
📱 Firefox
×
FRAMES
2455
APPS
10
UI EVENTS
3628
AUDIO
0
ACTIVE PERIOD
(TIMES IN LOCAL TIMEZONE)
11:35 → 00:10
TIME PER APP
— CLICK TO FILTER ALL PANELS BY APP
iTerm2
0.9m
Claude
0.5m
Firefox
0.3m
Alfred
0.3m
Websites
Windows
UI Events
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-18909
0.1m
mail.google.com/mail/u/0/#inbox
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20543
0.1m
jiminny.atlassian.net/jira/software/c/projects/JY/boards/37
0m
app.dev.jiminny.com
0m...
|
7516
|
|
7778
|
145
|
21
|
2026-04-13T16:23:30.178110+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776097410178_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Pause","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loading:","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Mute","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Exit Full Screen","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
3870500721152908643
|
8108599707473481852
|
click
|
hybrid
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxF Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplallA100% <47Mon 13 Apr 19:23:30-zshT81DOCKER• 881DEV (-zsh)О ₴2APP (-zsh)• *3-zsh• ×4-zsh• ₴5|-zsh886-zshO 87* Unable to acce...* *8{"app_name":"QuickTimePlayer""n" :16},{"app_name" : "UserNotificationCenter"',"n":4},{"app_name"::"Activity Monitor"{"app_name" : "NetAuthAgent","n":4},,"n":3},{"app_name": "Control Centre", "n" :3},{"app_name" : "Slack","n" :2},{"app_name":"Raycast","n":2},{"app_name": "System Settings","n" :1},{"app_name" : "Preview"."n":1}]lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $sqlite3 -column -header ~/.screenpipe/db.sqlite "SELECT timestamp, app_name, window_name FROM frames ORDER BY timestamp DESC LIMIT10;"timestampapp_namewindow_name2026-04-12113:33:14.649009+00:00iTerm22026-04-12T13:33:13.963922+00:002026-04-12T13:33:13.415261+00:002026-04-12T13:33:12.521412+00:002026-04-12T13:33:09.128387+00:002026-04-12T13:33:06.137541+00:002026-04-12T13:33:05.165254+00:00iTerm2iTerm2iTerm2Claude2026-04-12113:32:50.425455+00:002026-04-12T13:32:49.108195+00:00Claude2026-04-12T13:32:45.339017+00:00iTerm2lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny-zsh-zsh-zsh-zsh-zsh"status": "healthy""frame_status": "ok""audio_status": "disabled","last_frame": "2026-04-13T15:11:31+03:00","uptime": 19271.640087958,"fps": 0.11841234008027791,"frames": 2282}4.0K256M418M64K196K24K132K132K132K32Klukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du-sh ~/.screenpipe/*/Users/lukas/.screenpipe/config.json/Users/lukas/.screenpipe/data/Users/lukas/.screenpipe/db.sqlite/Users/lukas/.screenpipe/db.sqlite-shm/Users/lukas/.screenpipe/db.sqlite-wal/Users/lukas/.screenpipe/pipes/Users/Lukas/.screenpipe/screenpipe.2026-04-09.0.10g/Users/lukas/.screenpipe/screenpipe.2026-04-11.0.log/Users/lukas/.screenpipe/screenpipe.2026-04-12.0.1og/Users/lukas/.screenpipe/screenpipe.2026-04-13.0.loglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du -sh ~/.screenpipe...
|
7777
|
|
7781
|
145
|
24
|
2026-04-13T16:23:33.630903+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776097413630_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Pause","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loading:","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Mute","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Exit Full Screen","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
3870500721152908643
|
8108599707473481852
|
click
|
hybrid
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxF Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplallA100% <47-zshMon 13 Apr 19:23:33T81DOCKER• 881DEV (-zsh)О ₴2APP (-zsh)• *3-zsh• ×4-zsh• ₴5|-zsh886-zshO x7* Unable to acce... ** 88{"app_name":"QuickTimePlayer""n" :16},{"app_name" : "UserNotificationCenter","n":4},{"app_name"::"Activity Monitor"{"app_name" : "NetAuthAgent","n":4},,"n":3},{"app_name": "Control Centre", "n" :3},{"app_name" : "Slack","n" :2},{"app_name":"Raycast","n":2},{"app_name": "System Settings","n" :1},{"app_name" : "Preview"."n":1}]lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 -column -header ~/.screenpipe/db.sqlite "SELECT timestamp, app_name, window_name FROM frames ORDER BY timestamp DESC LIMIT10;"timestampapp_namewindow_name2026-04-12T13:33:14.649009+00:00iTerm22026-04-12T13:33:13.963922+00:002026-04-12T13:33:13.415261+00:002026-04-12T13:33:12.521412+00:002026-04-12T13:33:09.128387+00:002026-04-12T13:33:06.137541+00:002026-04-12T13:33:05.165254+00:00iTerm2iTerm2iTerm2Claude2026-04-12113:32:50.425455+00:002026-04-12T13:32:49.108195+00:00Claude2026-04-12T13:32:45.339017+00:00iTerm2lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny-zsh-zsh-zsh-zsh-zsh"status": "healthy""frame_status": "ok""audio_status": "disabled","last_frame": "2026-04-13T15:11:31+03:00","uptime": 19271.640087958,"fps": 0.11841234008027791,"frames": 2282}4.0K256M418M64K196K24K132K132K132K32Klukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du-sh ~/.screenpipe/*/Users/lukas/.screenpipe/config.json/Users/lukas/.screenpipe/data/Users/lukas/.screenpipe/db.sqlite/Users/lukas/.screenpipe/db.sqlite-shm/Users/lukas/.screenpipe/db.sqlite-wal/Users/lukas/.screenpipe/pipes/Users/Lukas/.screenpipe/screenpipe.2026-04-09.0.10g/Users/lukas/.screenpipe/screenpipe.2026-04-11.0.log/Users/lukas/.screenpipe/screenpipe.2026-04-12.0.1og/Users/lukas/.screenpipe/screenpipe.2026-04-13.0.loglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du -sh ~/.screenpipe...
|
NULL
|
|
7783
|
145
|
26
|
2026-04-13T16:23:35.680964+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776097415680_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Pause","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loading:","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Mute","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Exit Full Screen","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
3870500721152908643
|
8108599707473481852
|
click
|
hybrid
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxF Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplallA100% <47Mon 13 Apr 19:23:35-zshT81DOCKER• 881DEV (-zsh)О ₴2APP (-zsh)• *3-zsh• ×4-zsh• 285-zsh886-zshO &7* Unable to acce...*- *8{"app_name":"QuickTimePlayer""n":16},{"app_name" : "UserNotificationCenter"',"n":4},{"app_name"::"Activity Monitor"{"app_name" : "NetAuthAgent","n":4},,"n":3},{"app_name": "Control Centre", "n" :3},{"app_name" : "Slack","n" :2},{"app_name":"Raycast","n":2},{"app_name": "System Settings","n" :1},{"app_name" : "Preview"."n":1}]lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $sqlite3 -column -header ~/.screenpipe/db.sqlite "SELECT timestamp, app_name, window_name FROM frames ORDER BY timestamp DESC LIMIT10;"timestampapp_namewindow_name2026-04-12T13:33:14.649009+00:00iTerm2-zsh2026-04-12T13:33:13.963922+00:002026-04-12T13:33:13.415261+00:002026-04-12T13:33:12.521412+00:002026-04-12T13:33:09.128387+00:002026-04-12T13:33:06.137541+00:002026-04-12T13:33:05.165254+00:00iTerm2iTerm2iTerm2Claude-zsh-zsh-zshClaude2026-04-12113:32:50.425455+00:002026-04-12T13:32:49.108195+00:002026-04-12T13:32:45.339017+00:00ClaudeiTerm2Claude-zshlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sp-status"status": "healthy""frame_status": "ok""audio_status": "disabled","last_frame": "2026-04-13T15:11:31+03:00","uptime": 19271.640087958,"fps": 0.11841234008027791,"frames": 2282}4.0K256M418M64K196K24K132K132K132K32Klukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du-sh ~/.screenpipe/*/Users/lukas/.screenpipe/config.json/Users/lukas/.screenpipe/data/Users/lukas/.screenpipe/db.sqlite/Users/lukas/.screenpipe/db.sqlite-shm/Users/lukas/.screenpipe/db.sqlite-wal/Users/lukas/.screenpipe/pipes/Users/Lukas/.screenpipe/screenpipe.2026-04-09.0.10g/Users/lukas/.screenpipe/screenpipe.2026-04-11.0.log/Users/lukas/.screenpipe/screenpipe.2026-04-12.0.1og/Users/lukas/.screenpipe/screenpipe.2026-04-13.0.loglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du -sh ~/.screenpipe...
|
NULL
|
|
7786
|
145
|
29
|
2026-04-13T16:23:38.114934+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776097418114_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Pause","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loading:","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Mute","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Exit Full Screen","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
3870500721152908643
|
8108599707473481852
|
click
|
hybrid
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxF Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplallA100% <47Mon 13 Apr 19:23:38-zshT81DOCKER• 881DEV (-zsh)О ₴2APP (-zsh)• *3-zsh• ×4-zsh• ₴5|-zsh886-zshO 87* Unable to acce...* *8{"app_name":"QuickTimePlayer""n" :16},{"app_name" : "UserNotificationCenter"',"n":4},{"app_name"::"Activity Monitor"{"app_name" : "NetAuthAgent","n":4},,"n":3},{"app_name": "Control Centre", "n" :3},{"app_name" : "Slack","n" :2},{"app_name":"Raycast","n":2},{"app_name": "System Settings","n" :1},{"app_name" : "Preview"."n":1}]lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $sqlite3 -column -header ~/.screenpipe/db.sqlite "SELECT timestamp, app_name, window_name FROM frames ORDER BY timestamp DESC LIMIT10;"timestampapp_namewindow_name2026-04-12113:33:14.649009+00:00iTerm22026-04-12T13:33:13.963922+00:002026-04-12T13:33:13.415261+00:002026-04-12T13:33:12.521412+00:002026-04-12T13:33:09.128387+00:002026-04-12T13:33:06.137541+00:002026-04-12T13:33:05.165254+00:00iTerm2iTerm2iTerm2Claude2026-04-12113:32:50.425455+00:002026-04-12T13:32:49.108195+00:00Claude2026-04-12T13:32:45.339017+00:00iTerm2lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny-zsh-zsh-zsh-zsh-zsh"status": "healthy""frame_status": "ok""audio_status": "disabled","last_frame": "2026-04-13T15:11:31+03:00","uptime": 19271.640087958,"fps": 0.11841234008027791,"frames": 2282}4.0K256M418M64K196K24K132K132K132K32Klukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du-sh ~/.screenpipe/*/Users/lukas/.screenpipe/config.json/Users/lukas/.screenpipe/data/Users/lukas/.screenpipe/db.sqlite/Users/lukas/.screenpipe/db.sqlite-shm/Users/lukas/.screenpipe/db.sqlite-wal/Users/lukas/.screenpipe/pipes/Users/Lukas/.screenpipe/screenpipe.2026-04-09.0.10g/Users/lukas/.screenpipe/screenpipe.2026-04-11.0.log/Users/lukas/.screenpipe/screenpipe.2026-04-12.0.1og/Users/lukas/.screenpipe/screenpipe.2026-04-13.0.loglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du -sh ~/.screenpipe...
|
7785
|
|
7790
|
145
|
33
|
2026-04-13T16:23:57.486803+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-13/1776 /Users/lukas/.screenpipe/data/data/2026-04-13/1776097437486_m1.jpg...
|
Firefox
|
Screenpipe Dashboard — Personal
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Pause","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Loading:","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Mute","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Exit Full Screen","depth":7,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
3870500721152908643
|
8108599707473481852
|
click
|
hybrid
|
NULL
|
Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxF Pause
Loading:
100%
Mute
Exit Full Screen
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplallA100% <47Mon 13 Apr 19:23:57-zshT81DOCKER• 881DEV (-zsh)О ₴2APP (-zsh)• *3-zsh• ×4-zsh• ₴5|-zsh886-zshO 87* Unable to acce...*8{"app_name":"QuickTimePlayer""n" :16},{"app_name" : "UserNotificationCenter"',"n":4},{"app_name"::"Activity Monitor"{"app_name" : "NetAuthAgent","n":4},,"n":3},{"app_name": "Control Centre", "n" :3},{"app_name" : "Slack","n" :2},{"app_name":"Raycast","n":2},{"app_name": "System Settings","n" :1},{"app_name" : "Preview"."n":1}]lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $sqlite3 -column -header ~/.screenpipe/db.sqlite "SELECT timestamp, app_name, window_name FROM frames ORDER BY timestamp DESC LIMIT10;"timestampapp_namewindow_name2026-04-12113:33:14.649009+00:00iTerm22026-04-12T13:33:13.963922+00:002026-04-12T13:33:13.415261+00:002026-04-12T13:33:12.521412+00:002026-04-12T13:33:09.128387+00:002026-04-12T13:33:06.137541+00:002026-04-12T13:33:05.165254+00:00iTerm2iTerm2iTerm2Claude2026-04-12113:32:50.425455+00:002026-04-12T13:32:49.108195+00:00Claude2026-04-12T13:32:45.339017+00:00iTerm2lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny-zsh-zsh-zsh-zsh-zsh"status": "healthy""frame_status": "ok""audio_status": "disabled","last_frame": "2026-04-13T15:11:31+03:00","uptime": 19271.640087958,"fps": 0.11841234008027791,"frames": 2282}4.0K256M418M64K196K24K132K132K132K32Klukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du-sh ~/.screenpipe/*/Users/lukas/.screenpipe/config.json/Users/lukas/.screenpipe/data/Users/lukas/.screenpipe/db.sqlite/Users/lukas/.screenpipe/db.sqlite-shm/Users/lukas/.screenpipe/db.sqlite-wal/Users/lukas/.screenpipe/pipes/Users/Lukas/.screenpipe/screenpipe.2026-04-09.0.10g/Users/lukas/.screenpipe/screenpipe.2026-04-11.0.log/Users/lukas/.screenpipe/screenpipe.2026-04-12.0.1og/Users/lukas/.screenpipe/screenpipe.2026-04-13.0.loglukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ S du -sh ~/.screenpipe...
|
7789
|
|
74507
|
1857
|
14
|
2026-04-23T10:02:06.507679+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776938526507_m1.jpg...
|
Firefox
|
Library
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"mazanoke-images-YWJ6.zip","depth":5,"bounds":{"left":0.15902779,"top":0.107777774,"width":0.103472225,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6.8 MB — lakylak.xyz — 13:02","depth":5,"bounds":{"left":0.15902779,"top":0.12722223,"width":0.10277778,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show in Finder","depth":4,"bounds":{"left":0.5229167,"top":0.09777778,"width":0.03888889,"height":0.055},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-636533346013606288
|
8107148863290213222
|
click
|
hybrid
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder
Firefox••→• HistoryDownloadsTagsAll BookmarksFileEditViewHistoryBookmarksToolsLibraryWindowHelpj Support Daily - in 1h 58 mClear DownloadsQ Search DownloadsA₴4100% (Thu 23 Apr 13:02:06181*5mazanoke-images-YWJ6.zip6.8 MB - lakylak.xyz — 13:02dockerLamp_1Team Aircall Demo (7980eSeb-b11c-4cee-9c21-8bb29ba85f3b) is not yet assignedan owner.skipping...docker_lamp_11 [HubSpot] Syncing objects for GoStudent UAT (b2d49a54-b645-4637-a7ae-a86cfce6e8e4) since 2026-02-17 15:09:59 (delay: 1s)docker_lamp_11 [HubSpot] Syncing objects for JustCall (c6b9d6b0-b48d-4832-a68c-a57d60651888) since 2026-02-17 15:07:41 (delay: 1s)docker_lamp_11 Team Twilio Video (c334ca55-b230-411c-b10e-31c8204bd07b) is not yet assignedan owner.skipping...docker_1amp_11 Team My Test Account 3000 (dbc9990d-b35f-4e38-9550-22cdd6059514)is notyetassignedan owner.skipping...docker_lamp_1I Team test (7997eb70-8aa4-491a-870d-311977568df4) isassignedanowner.skipping...docker_lamp_1I Team Test (Se06dcee-0613-470e-9a77-2c283198f3bf) is not yetassignedanowner.skipping...docker_lamp_1I Team test ogg auto sync (da44776e-306f-427a-83d8-a1b4baa5537e) isnot yet assigned an owner., skipping...docker_lamp_11 Team Tourlaner (d9b71080-388b-4cf5-8175-aa0f29bee635)is noted an owner.skipping…..yetassigndocker_1amp_1I Dispatched 4 HubSpot sync jobs83-zsh)screenpipe"-zshQThu Apr 23 12:12:54 on consoled not find a pyproject.toml file in /Users/lukas or its parentsd not find a pyproject.toml file in /Users/lukas or its parents-Kovaliks-MacBook-Pro-Jiminny ~ $ U|sh)Thu Apr 23 12:12:54 on consoled not find a pyproject.toml file in /Users/lukas or its parentsd not find a pyproject.tomlfile in /Users/lukas or its parentsKovaliks-MacBook-Pro-Jiminny ~ $ I|E (-zsh)Thu Apr 23 12:12:54 on console.d not find a pyproject.toml file in /Users/lukas or its parents.d not find a pyproject.toml file in /Users/lukas or its parents-Kovaliks-MacBook-Pro-Jiminnysh)Thu Apr 23 12:13:49on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX T6FE (-zsh)Last login: Thu Apr 23 12:13:49on ttys003Poetrycould not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|PRODSTAGEFRONTENDEXTENSION...
|
NULL
|
|
74508
|
1858
|
21
|
2026-04-23T10:02:07.916469+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776938527916_m2.jpg...
|
Firefox
|
Library
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"mazanoke-images-YWJ6.zip","depth":5,"bounds":{"left":0.3464096,"top":1.0,"width":0.049534574,"height":-0.077414155},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6.8 MB — lakylak.xyz — 13:02","depth":5,"bounds":{"left":0.3464096,"top":1.0,"width":0.04920213,"height":-0.091380715},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show in Finder","depth":4,"bounds":{"left":0.5206117,"top":1.0,"width":0.01861702,"height":-0.07023144},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-636533346013606288
|
8107148863290213222
|
visual_change
|
hybrid
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder
HomdActivityMore10019Q Describe what you are loJiminny ... ~& Q. Adelin"TMore unrea# product lauic random# releasesFavouritesjiminny# sofia-office(*) AirDrop# support• Recents# thank-yous"the neonle(^ Downloads6? Direct message1n lukasAdelina Petro(3) Aneliva AngiCloud• iCloud DriveStovan TomdP. Nikolay Yank999 Svnc folderf Petko KashinLocationsD DXP4800PLUS-B5F AR Aneliva Angee Nikolay Niko48 Network# Mario Georg TagsSe: Todor Stama• CRMA Gabriela Dun• OrangelVasil Vasilev• RedGalva Dimitr• YellowR Stefka Stoya• Green* Stoyan Tanev• Purple::: AnndToast() All Taas.$i Jira Cloud1Cosole Coldmaces.lakylak.xyzgifsvgavel LeIIIco• BrowseP! Western Digital Red Plus 3.5 6TB 5400rpm 256MB SAa Today's Dealsinstall screenpipe - screenpipe docsNew TablA Screenpipe - Archive(SQLite Web: archive.de(*) SQLite Web: db.sqliteG rescue time detailed overview - Google SearchG how would I use screenpipe effectively - Google Searc/\ MAZANOKE | Online Image Optimizer That Runs Pr X— New TabSettingsW Images 4ImagesOptimized images ready for review and download.• Delete allИскане Даниел Коваликioа175 MB-49.64%JPGФактура Април даниел Ковалик.ipg2024y4032Фактура Март Даниел Ковалик.jpg3024x40S2Искане Априана Ковалик.ioa200441091.60 MB -54.17%JPG165 MB -54.09%JPGI1.83 MB-4918%JPGsupport Dally • In 1h 00m100% 52InU ZS Aor 13:02-01~ Download al* Download• Download* DownloadJ, Download...
|
74506
|
|
74544
|
1857
|
25
|
2026-04-23T10:03:09.257361+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776938589257_m1.jpg...
|
Firefox
|
Library
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"mazanoke-images-YWJ6.zip","depth":5,"bounds":{"left":0.15902779,"top":0.107777774,"width":0.103472225,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6.8 MB — lakylak.xyz — 13:02","depth":5,"bounds":{"left":0.15902779,"top":0.12722223,"width":0.10277778,"height":0.014444444},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show in Finder","depth":4,"bounds":{"left":0.5229167,"top":0.09777778,"width":0.03888889,"height":0.055},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-636533346013606288
|
8107148863290213222
|
click
|
hybrid
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder
Firefox••→• HistoryDownloadsTags* All BookmarksFileEditViewHistoryBookmarksToolsLibraryWindowHelpClear DownloadsQSearch Downloadsmazanoke-images-YWJ6.zip6.8 MB - lakylak.xyz - 13:02dockerLamp_1Team Aircall Demo (7980eSeb-b11c-4cee-9c21-8bb29ba85f3b) is not yet assignedan owner.skipping...docker_lamp_11 [HubSpot]Syncing objects for GoStudent UAT (b2d49a54-b645-4637-a7ae-a86cfce6e8e4) since 2026-02-17 15:09:59 (delay: 1s)docker_lamp_11 [HubSpot] Syncing objects for JustCall (c6b9d6b0-b48d-4832-a68c-a57d60651888) since 2026-02-17 15:07:41 (delay: 1s)docker_lamp_11 Team Twilio Video (c334ca55-b230-411c-b10e-31c8204bd07b) is not yet assignedan owner.skipping...docker_1amp_11 Team My Test Account 3000 (dbc9990d-b35f-4e38-9550-22cdd6059514)is notyetassignedan owner.skipping...docker_lamp_1I Team test (7997eb70-8aa4-491a-870d-311977568df4) isnotyetassignedanowner.skipping...docker_lamp_1I Team Test (Se06dcee-0613-470e-9a77-2c283198f3bf) is not yetassignedanowner.skipping...docker_lamp_1I Team test ogg auto sync (da44776e-306f-427a-83d8-a1b4baa5537e)isnot yet assigned an owner., skipping...docker_lamp_11 Team Tourlaner (d9b71080-388b-4cf5-8175-aa0f29bee635)is notyetassigned an owner.skipping…..docker_1amp_1I Dispatched 4 HubSpot sync jobsj Support Daily - in 1h 57 mA100% (83-zsh)screenpipe"O $4-zshQThu Apr 23 12:12:54 on consoled not find a pyproject.toml file in /Users/lukas or its parentsd not find a pyproject.toml file in /Users/lukas or its parents-Kovaliks-MacBook-Pro-Jiminny ~ $ |sh)Thu Apr 23 12:12:54 on consoled not find a pyproject.toml file in /Users/lukas or its parentsd not find a pyproject.tomlfile in /Users/lukas or its parentsKovaliks-MacBook-Pro-Jiminny ~ $ I|E (-zsh)Thu Apr 23 12:12:54 on console.d not find a pyproject.toml file in /Users/lukas or its parents.d not find a pyproject.toml file in /Users/lukas or its parents-Kovaliks-MacBook-Pro-Jiminnysh)Thu Apr 23 12:13:49on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX T6FE (-zsh)Last login: Thu Apr 23 12:13:49on ttys003Poetrycould not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|Thu 23 Apr 13:03:09*5STAGE8FRONTENDEXTENSION...
|
74543
|
|
74545
|
1858
|
47
|
2026-04-23T10:03:09.256898+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776938589256_m2.jpg...
|
Firefox
|
Library
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"mazanoke-images-YWJ6.zip","depth":5,"bounds":{"left":0.3464096,"top":1.0,"width":0.049534574,"height":-0.077414155},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6.8 MB — lakylak.xyz — 13:02","depth":5,"bounds":{"left":0.3464096,"top":1.0,"width":0.04920213,"height":-0.091380715},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show in Finder","depth":4,"bounds":{"left":0.5206117,"top":1.0,"width":0.01861702,"height":-0.07023144},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-636533346013606288
|
8107148863290213222
|
click
|
hybrid
|
NULL
|
mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13 mazanoke-images-YWJ6.zip
6.8 MB — lakylak.xyz — 13:02
Show in Finder
TrasswordAccountsWindowhelt0 # Support Daily - in 1h 57m100% LzInu z3 Aor 13.03.00www.dskdirect.bg/page/default.aspx?xml_id=/bg-BG/.login@ EnglishWestern Diaital Red Plus 3.5 6TB 5400rom 256MB SAa Today's Dealsinstall screenpipe - screenpipe docsNew TablA Screenpipe - Archive(SQLite Web: archive.de@ SQLite Web: db.sqliteС дскдиректКгаомосашшмоци О пиmоnцом Uamomo nalonoActivityJiminny ...E 0. Adelin"TMore unrea# product_laur# random# releases# sofia-office# support# thank-yous# the_people_Favouritesjiminny* AirDrop• Recents^ Direct message• Downloads.1n lukasP. Adelina Petri3 Aneliya Ange8. Stoyan TomoiCloudiCloud Drive999 Svnc folderP. Nikolay Yank&. Petko KashirP. Aneliya AngeP. Nikolay NikoLocationsQ DXP4800PLUS-B5F A® Network8. Mario GeorgTags. Todor Stamal• CRMf. Gabriela Dur• OrangeC. Vasil Vasilev• RedP. Galya Dimitr• Yellow. Stefka Stoyal• Green2. Stoyan Tanev## Apps• PurpleO All Tags...® Toastf Jira Cloud1Cosole ColdG rescue time detailed overview - Google SearchG how would I use screenpipe effectively - Google SearcЕлектронно банкиране ДСК Директ от Банка ДС— New TabПроCu!AkmиИзг02.04.202605.01.202622.10.202503.10.202505.09.202524.07.202501.07.2026Enter [PASSWORD_FIELD] confirmation is set to every 14 days.Настоящият сайт използва бисkßumkиинрормация: ПаСК диРе уя за полбаквumku (cookies), за gа персонализирате, изберете опция "Настройки". За повече•г оиемам всички...
|
74542
|
|
61626
|
1328
|
48
|
2026-04-21T07:05:52.360681+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776755152360_m1.jpg...
|
QuickTime Player
|
Daily 2026-04-21.mp4
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
12:30
toggle elapsed time, timecode and framecount
14:32
toggle duration and remaining time
document actions
Daily 2026-04-21.mp4...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"rewind","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"play/pause","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":true},{"role":"AXButton","text":"fast forward","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"mute","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"More Controls","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"toggle full screen","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":1,"role_description":"button","is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":2,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show media selection menu","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"toggle picture-in-picture playback","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show action menu","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"share","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show chapter menu","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"playback speed","depth":1,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"12:30","depth":1,"role_description":"text"},{"role":"AXCheckBox","text":"toggle elapsed time, timecode and framecount","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"14:32","depth":1,"role_description":"text"},{"role":"AXCheckBox","text":"toggle duration and remaining time","depth":1,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"document actions","depth":1,"role_description":"menu button","is_enabled":false,"is_focused":false},{"role":"AXStaticText","text":"Daily 2026-04-21.mp4","depth":1,"role_description":"text"}]...
|
-125839551293059722
|
8105965327724841590
|
click
|
hybrid
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
12:30
toggle elapsed time, timecode and framecount
14:32
toggle duration and remaining time
document actions
Daily 2026-04-21.mp4
System SettingsFileApril 2soun+EEST08:0009:0010:0511:0012:0013:0014:0015:0016:00SoundSound inputSound outputSound volumeAlerts and sound effectsMute the soundAccessibilityBackground soundsBackgroundsounds volumeFlash the screen when analert sound occursPlay sound whencommand is recognisedPlay soundsPlay sounds for keys anddwell actionsPlay system soundsSelect Background SoundTurn off backgroundsounds when your Mac isnot in useUse click key soundsHearing DevicesBluetoothBluetooth audioControl CentreShow volume in themenu barNetworkEditViewWindowHelpSoundSound EffectsAlert soundPlay sound effects throughAlert volumePlay sound on startupPlay user interface sound effectsPlay feedback when volume is changedOutput & InputOutputNameDELL U3821DWMacBook Pro Speakerssoundcore AeroClipLiving Room TVBoop € Osoundcore AeroClip €InputTyрeDisplayPortBuilt-inBluetoothAirPlayApplications may be able to access head pose information when playingspatialised audio.Output volume4 H-(*))• MuteBalanceLeftRightSupport Daily - in 4h 55 mWeek vTodaySat 25Sun 26MEMORY PRESSURE185,7 MBPhysical Memory:Memory Used:Cached Files:Swap Used:100% <8Tue 21 Apr 10:05:52CPUMemoryEnergyDiskThreadsPorts PID ,00 GB13,61 GB2,34 GB2,25 GBApp Memory:Wired Memory:Compressed:NetworkUserlukaslukaslukas_windowserverlukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukas4,25 GB2,64 GB6,17 GB...
|
61624
|
|
61627
|
1329
|
40
|
2026-04-21T07:05:52.256594+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776755152256_m2.jpg...
|
QuickTime Player
|
Daily 2026-04-21.mp4
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
12:30
toggle elapsed time, timecode and framecount
14:32
toggle duration and remaining time
document actions
Daily 2026-04-21.mp4...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"rewind","depth":1,"bounds":{"left":0.54521275,"top":0.72027135,"width":0.00831117,"height":0.012769354},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"play/pause","depth":1,"bounds":{"left":0.55701464,"top":0.7134876,"width":0.009640957,"height":0.027134877},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":true},{"role":"AXButton","text":"fast forward","depth":1,"bounds":{"left":0.5703125,"top":0.72027135,"width":0.00831117,"height":0.012769354},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"mute","depth":1,"bounds":{"left":0.48919547,"top":0.72027135,"width":0.007480053,"height":0.011971269},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"More Controls","depth":1,"bounds":{"left":0.6284907,"top":0.7198723,"width":0.005984043,"height":0.012769354},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"toggle full screen","depth":1,"bounds":{"left":0.59857047,"top":0.72426176,"width":0.0066489363,"height":0.015961692},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":1,"bounds":{"left":0.59857047,"top":0.71907425,"width":0.0066489363,"height":0.015961692},"role_description":"button","is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":2,"bounds":{"left":0.59857047,"top":0.71907425,"width":0.0066489363,"height":0.015961692},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show media selection menu","depth":1,"bounds":{"left":0.59857047,"top":0.72426176,"width":0.00731383,"height":0.015961692},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"toggle picture-in-picture playback","depth":1,"bounds":{"left":0.61120343,"top":0.7186752,"width":0.00831117,"height":0.015961692},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show action menu","depth":1,"bounds":{"left":0.59857047,"top":0.7238627,"width":0.006981383,"height":0.016759777},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"share","depth":1,"bounds":{"left":0.62549865,"top":0.71628094,"width":0.006482713,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show chapter menu","depth":1,"bounds":{"left":0.59857047,"top":0.72625697,"width":0.006981383,"height":0.011971269},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"bounds":{"left":0.59857047,"top":0.7226656,"width":0.0066489363,"height":0.01915403},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"bounds":{"left":0.59857047,"top":0.7246608,"width":0.00831117,"height":0.015163607},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"playback speed","depth":1,"bounds":{"left":0.59857047,"top":0.7246608,"width":0.0063164895,"height":0.015163607},"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"12:30","depth":1,"bounds":{"left":0.48919547,"top":0.74660814,"width":0.012632979,"height":0.011971269},"role_description":"text"},{"role":"AXCheckBox","text":"toggle elapsed time, timecode and framecount","depth":1,"bounds":{"left":0.4898604,"top":0.74660814,"width":0.011303191,"height":0.011971269},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"14:32","depth":1,"bounds":{"left":0.6193484,"top":0.74660814,"width":0.01512633,"height":0.011971269},"role_description":"text"},{"role":"AXCheckBox","text":"toggle duration and remaining time","depth":1,"bounds":{"left":0.6200133,"top":0.74660814,"width":0.013796543,"height":0.011971269},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"document actions","depth":1,"bounds":{"left":0.59042555,"top":0.21947326,"width":0.0033244682,"height":0.012769354},"role_description":"menu button","is_enabled":false,"is_focused":false},{"role":"AXStaticText","text":"Daily 2026-04-21.mp4","depth":1,"bounds":{"left":0.53956115,"top":0.21947326,"width":0.05086436,"height":0.012769354},"role_description":"text"}]...
|
-125839551293059722
|
8105965327724841590
|
click
|
hybrid
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
12:30
toggle elapsed time, timecode and framecount
14:32
toggle duration and remaining time
document actions
Daily 2026-04-21.mp4
Cuickllme PlayercaltVIeWWindowMelpy.atlassian.net/jira/software/c/projects/JY/boards/37O JIMINNYIY [SRD-6793] Les Mills activity type(SRD-6787] Issue with reconnectin@ For you© Recent|# StarredX Jiminny MCP Connector - Product0+ Apps|+ (UY-20676) Notify the user if a PanQ Spaces+.*.M Jiminny MailJiminny (New)(JY-20500) Batch initial sync for S.ull Plarorm leamFeed - jiminny - Sentry& Jiminny() JY-20701 | Reschedule HubSpot SPipelines - jiminny/appNew TabIID SE KanbanII Capture TeamW Enterprise Stability I...ID Processing TeamC Service-Desk= More spacesService-Desk - Queues - Platforn— Filters_ New TabCB DashboardsC÷ OperationsI2 Confluence: Teams"= Customise sidebarISpaces / Jiminny (New)Platform TeamSummary& TimelineE BacklogII Active sprints@ Calendar• Search boardi00001Epic vType vREADY FOR DEV 7IN DEV 2Rework Nudges - Phase 2 - changeNudaes to use the indexed at periodCOST-EFFECTIVE AND FASTER NUDGESBacklogChange foreveexpirationCOST-EFFECTIV BIn Dev |N JY-20489… JY-9712Investigate and fix why exceedronrawesome packade limirsiMAINTENANCEReady for DevAI Review - QOKey PointsGROWTH - MAINIn Devl JY-20564|.JY-20566AI Reports > Empty page design andpromotionAJ REPORTSBacklog[ JY-20372Send emall notitication when the revort isinot generatedAJ REPORTSBacklog[ JY-201572 •000=|Notify a user before the AJ Report expiresAJ REPORTSBacklog[ JY-205081 ..00=@Svnc obbortunities without a local owner(user_id is null)PLATFORM STABILITYBacklogEJY-2035212 ReportsQ Search4 Testing Board# ListE Formse Components⅔› Development⅘> CodeD Daily 2026-04-21.mp48 A E Datad+ CreateO Security• Releases• DeploymentsE Archived work itemsE DocsPlatform Team 88# JY-12251| W JY-20566want to see how our features are tracking comparing to new models that are coming out.• Summary on Call• Action Items on Call• AA on Call• Identity participants for mono calls" JY-20567 CLONE - Summary evaluationNikola)Nikolay Yankov3 othersSteliyan G..* Steliyan Georgiev2 Galya DimitrovaQuick start development• Onen with VS CACreate branc)teliyan GeorgievLukas Kovatik9:58 AM | Daily - Platformsuppont Dally • In 4h oom100% LzTue 21 Apr 10:05:52Ask Rovo@ ShortcutsvSlack integration& Reporting CenterGroup: QueriesDEPLOY 7Prepare fallback with email for SSO forpersistent name_id_ tormatREDUCE CHURNClosed… JY-20632m•ee=Au Panorama> Don't show internal errorsito customersASK ANYTHING ON ANYTHINGDeployedProphet)+ JY-2027811• еее —Upgrade Python and libraries - AprMAINTENANCEDeployedE JY-19967|1@ ••=CLONE - [Team insights] Filter gets resetautomaticallv(SUPPORT TICKETSDeolovedIYY JY-206810.5 ? •edTssue with reconnectina 7oholSUPPORT TICKETSDeployedX* JY-20692Toch Navl Tmnrove Nenendahot Rot 8ClosedIEJY-20696Les Mills activity types not pulling in...
|
NULL
|
|
51675
|
1118
|
25
|
2026-04-20T06:17:32.041285+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776665852041_m1.jpg...
|
Firefox
|
JY-20543 add AJ reports User pilot tracking by Lak JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11932
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
5 days ago
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 2 weeks ago •
claude
claude
bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-20543 add AJ reports User pilot tracking #11932 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11932","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Merged","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"merged 6 commits into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-automated-reports-ask-jiminny","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-automated-reports-ask-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20543-AJ-report-tracking","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543-AJ-report-tracking","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lines changed: 284 additions & 1 deletion","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (4)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (6)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (7)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 2 weeks ago","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No description provided.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"add AJ reports User pilot tracking","depth":14,"help_text":"JY-20543 add AJ reports User pilot tracking","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"add AJ reports User pilot tracking","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"2 / 4 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"609200a","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"609200a","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"code smells","depth":14,"help_text":"JY-20543 code smells","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"code smells","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"8 / 10 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"21ab308","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21ab308","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix test","depth":14,"help_text":"JY-20543 fix test","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix test","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 21 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"93a69eb","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"93a69eb","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix event","depth":14,"help_text":"JY-20543 fix event","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix event","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 22 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"01241ae","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"01241ae","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested review from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Vasil-Jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vasil-Jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"nikolaybiaivanov","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"approved these changes","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View reviewed changes","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View reviewed changes","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 2 weeks ago","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 2 weeks ago •","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 11s","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.19166666,"top":0.0,"width":0.5375,"height":0.024444444},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.19166666,"top":0.0,"width":0.07361111,"height":0.023333333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.19652778,"top":0.016666668,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gather context","depth":18,"bounds":{"left":0.20833333,"top":0.015,"width":0.06944445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.19652778,"top":0.044444446,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read changed files","depth":18,"bounds":{"left":0.20833333,"top":0.042777777,"width":0.088194445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.19652778,"top":0.072222225,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Analyze code and provide review feedback","depth":18,"bounds":{"left":0.20833333,"top":0.07055555,"width":0.19479166,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via","depth":17,"bounds":{"left":0.19166666,"top":0.15333334,"width":0.5138889,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ShouldQueue","depth":18,"bounds":{"left":0.19479166,"top":0.17944445,"width":0.05486111,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and solid test coverage. A few things worth flagging:","depth":17,"bounds":{"left":0.25277779,"top":0.17666666,"width":0.24166666,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Issues","depth":16,"bounds":{"left":0.19166666,"top":0.22444445,"width":0.5375,"height":0.019444445},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Issues","depth":17,"bounds":{"left":0.19166666,"top":0.225,"width":0.029861111,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Silent error swallowing — no logging on","depth":18,"bounds":{"left":0.19166666,"top":0.2638889,"width":0.19652778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":19,"bounds":{"left":0.39166668,"top":0.26666668,"width":0.074652776,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.19479166,"top":0.29,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L44–45","depth":17,"bounds":{"left":0.59131944,"top":0.28722224,"width":0.03784722,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"bounds":{"left":0.20277777,"top":0.34555554,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"bounds":{"left":0.2125,"top":0.34555554,"width":0.025,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"bounds":{"left":0.2375,"top":0.34555554,"width":0.08958333,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.32708332,"top":0.34555554,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.33194444,"top":0.34555554,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"bounds":{"left":0.20277777,"top":0.34555554,"width":0.14930555,"height":0.035},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.22256945,"top":0.36444443,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.22743055,"top":0.36444443,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.24756944,"top":0.36444443,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"bounds":{"left":0.25729167,"top":0.36444443,"width":0.035069443,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.2923611,"top":0.36444443,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"bounds":{"left":0.29722223,"top":0.36444443,"width":0.019791666,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"bounds":{"left":0.20277777,"top":0.36444443,"width":0.124305554,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.7,"top":0.335,"width":0.023611112,"height":0.039444443},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:","depth":17,"bounds":{"left":0.19166666,"top":0.43944445,"width":0.534375,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"bounds":{"left":0.20277777,"top":0.52055556,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"bounds":{"left":0.2125,"top":0.52055556,"width":0.025,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"bounds":{"left":0.2375,"top":0.52055556,"width":0.08958333,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.32708332,"top":0.52055556,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.33194444,"top":0.52055556,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"bounds":{"left":0.20277777,"top":0.52055556,"width":0.14930555,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.22256945,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.22743055,"top":0.54,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.24756944,"top":0.54,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"bounds":{"left":0.25729167,"top":0.54,"width":0.029861111,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.28715277,"top":0.54,"width":0.010069445,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"bounds":{"left":0.29722223,"top":0.54,"width":0.034722224,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.33194444,"top":0.54,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.33715278,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UserPilot tracking failed for automated report","depth":17,"bounds":{"left":0.3420139,"top":0.54,"width":0.22881944,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.5708333,"top":0.54,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"bounds":{"left":0.57604164,"top":0.54,"width":0.014930556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.59097224,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exception","depth":17,"bounds":{"left":0.59583336,"top":0.54,"width":0.044791665,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.640625,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"bounds":{"left":0.6454861,"top":0.54,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.665625,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.6704861,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.6753472,"top":0.54,"width":0.010069445,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getMessage","depth":17,"bounds":{"left":0.68541664,"top":0.54,"width":0.049652778,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"()]);","depth":17,"bounds":{"left":0.20277777,"top":0.54,"width":0.5572917,"height":0.035},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.22256945,"top":0.5588889,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.22743055,"top":0.5588889,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.24756944,"top":0.5588889,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"bounds":{"left":0.25729167,"top":0.5588889,"width":0.035069443,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.2923611,"top":0.5588889,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"bounds":{"left":0.29722223,"top":0.5588889,"width":0.019791666,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"bounds":{"left":0.20277777,"top":0.5588889,"width":0.124305554,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.7,"top":0.51055557,"width":0.023611112,"height":0.039444443},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"bounds":{"left":0.19166666,"top":0.6338889,"width":0.043055557,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"bounds":{"left":0.19166666,"top":0.6338889,"width":0.043055557,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Partial retry risk — duplicate tracking on queue retry","depth":18,"bounds":{"left":0.19166666,"top":0.71444446,"width":0.25659722,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.19479166,"top":0.7405556,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L36–47","depth":17,"bounds":{"left":0.59131944,"top":0.73777777,"width":0.0375,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If tracking succeeds for user #1 and fails for user #2 (throwing","depth":17,"bounds":{"left":0.19166666,"top":0.7788889,"width":0.28194445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":18,"bounds":{"left":0.4767361,"top":0.7816667,"width":0.074652776,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), the job is released and retried — which will re-track user #1. For","depth":17,"bounds":{"left":0.19166666,"top":0.7788889,"width":0.5170139,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exec_summary","depth":18,"bounds":{"left":0.33506945,"top":0.805,"width":0.059722222,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.","depth":17,"bounds":{"left":0.19166666,"top":0.80222225,"width":0.51180553,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Missing return type hint on","depth":18,"bounds":{"left":0.19166666,"top":0.9061111,"width":0.13993056,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":19,"bounds":{"left":0.33506945,"top":0.9088889,"width":0.059722222,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.19479166,"top":0.93222225,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L62","depth":17,"bounds":{"left":0.59131944,"top":0.92944443,"width":0.019791666,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"bounds":{"left":0.20277777,"top":0.9872222,"width":0.034722224,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"bounds":{"left":0.24236111,"top":0.9872222,"width":0.039930556,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload(","depth":17,"bounds":{"left":0.28229168,"top":0.9872222,"width":0.06979167,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReport","depth":17,"bounds":{"left":0.35208333,"top":0.9872222,"width":0.074652776,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.43159723,"top":0.9872222,"width":0.0048611113,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"bounds":{"left":0.43645832,"top":0.9872222,"width":0.074652776,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"bounds":{"left":0.51111114,"top":0.9872222,"width":0.014930556,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":17,"bounds":{"left":0.5260417,"top":0.9872222,"width":0.025,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.7,"top":0.9772222,"width":0.023611112,"height":0.022777796},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The return type annotation","depth":17,"bounds":{"left":0.19166666,"top":1.0,"width":0.12256944,"height":-0.046111107},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":18,"bounds":{"left":0.31770834,"top":1.0,"width":0.024652777,"height":-0.04888892},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is missing (the method has no return type declared). Minor but inconsistent with the","depth":17,"bounds":{"left":0.34583333,"top":1.0,"width":0.37986112,"height":-0.046111107},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"bounds":{"left":0.19479166,"top":1.0,"width":0.114583336,"height":-0.07222223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"discipline in the file.","depth":17,"bounds":{"left":0.3125,"top":1.0,"width":0.09201389,"height":-0.06944442},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Minor inefficiency — payload built before users are resolved","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L35–36","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"payload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"resolveUsers","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8557470894429586835
|
8098701052298063752
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
5 days ago
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 2 weeks ago •
claude
claude
bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {...
|
NULL
|
|
51674
|
1119
|
33
|
2026-04-20T06:17:31.972825+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776665851972_m2.jpg...
|
Firefox
|
JY-20543 add AJ reports User pilot tracking by Lak JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11932
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
5 days ago
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 2 weeks ago •
claude
claude
bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {
Copy
buildPayload
is called even when
resolveUsers
returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the
foreach
or after a guard on the users list.
What looks good
What looks good
Clean event/listener separation —
AutomatedReportGenerated
event is minimal and focused
getRecipientUserObjects
is a sensible addition to
AutomatedReportsService
and properly typed
Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered
Correct use of
Constants::QUEUE_DELAYABLE
for the queue name
EventDispatcher
properly injected in
ReportController
and tested
Add or remove reactions
yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
reviewed
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
Comment thread
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
38
+
$payload = $this->buildPayload($automatedReport);
39
+
40
+
try {
41
+
foreach ($this->resolveUsers($automatedReport) as $user) {
38
39
40
41
+
$payload = $this->buildPayload($automatedReport);
+...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.15525267,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.06981383,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.15924202,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.18994413,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.21707901,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.05501995,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.0056515955,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.16389628,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.17037898,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18134974,"top":0.10574621,"width":0.01512633,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20212767,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21326463,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23503989,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24601063,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"bounds":{"left":0.2606383,"top":0.09936153,"width":0.070644945,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27244017,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31881648,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"bounds":{"left":0.32180852,"top":0.113727055,"width":0.0056515955,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.3274601,"top":0.113727055,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33394283,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34524602,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.3678524,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.37898937,"top":0.10574621,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.09325133,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.34973404,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.48454124,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.98636967,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-20543 add AJ reports User pilot tracking #11932 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.19193934,"width":0.25116357,"height":0.031923383},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking","depth":14,"bounds":{"left":0.33776596,"top":0.19273743,"width":0.20162898,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.54205453,"top":0.19273743,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11932","depth":15,"bounds":{"left":0.54853725,"top":0.19273743,"width":0.028424202,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.57829124,"top":0.19513169,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.19832402,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.20430966,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Merged","depth":13,"bounds":{"left":0.34840426,"top":0.23623304,"width":0.017121011,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"bounds":{"left":0.3721742,"top":0.2330407,"width":0.01861702,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"bounds":{"left":0.3721742,"top":0.23463687,"width":0.01861702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"merged 6 commits into","depth":15,"bounds":{"left":0.39212102,"top":0.23463687,"width":0.049700797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-automated-reports-ask-jiminny","depth":15,"bounds":{"left":0.4431516,"top":0.23264167,"width":0.09507979,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-automated-reports-ask-jiminny","depth":16,"bounds":{"left":0.44514626,"top":0.235834,"width":0.091090426,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.53956115,"top":0.23463687,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20543-AJ-report-tracking","depth":16,"bounds":{"left":0.55086434,"top":0.23264167,"width":0.06881649,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543-AJ-report-tracking","depth":17,"bounds":{"left":0.55285907,"top":0.235834,"width":0.06482713,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.62101066,"top":0.23024741,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":16,"bounds":{"left":0.63164896,"top":0.23463687,"width":0.0234375,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lines changed: 284 additions & 1 deletion","depth":14,"bounds":{"left":0.70994014,"top":0.28651237,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (4)","depth":16,"bounds":{"left":0.33776596,"top":0.26855546,"width":0.057513297,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35139626,"top":0.27813247,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38962767,"top":0.27813247,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":18,"bounds":{"left":0.39245346,"top":0.27813247,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.39544547,"top":0.27813247,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (6)","depth":16,"bounds":{"left":0.39527926,"top":0.26855546,"width":0.048204787,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.4089096,"top":0.27813247,"width":0.018949468,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.43783244,"top":0.27813247,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"bounds":{"left":0.44082448,"top":0.27813247,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.44381648,"top":0.27813247,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"bounds":{"left":0.44348404,"top":0.26855546,"width":0.045212764,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.45711437,"top":0.27813247,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.48304522,"top":0.27813247,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.48603722,"top":0.27813247,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.48886302,"top":0.27813247,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (7)","depth":16,"bounds":{"left":0.4886968,"top":0.26855546,"width":0.05867686,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.50232714,"top":0.27813247,"width":0.029753989,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.54172206,"top":0.27813247,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7","depth":18,"bounds":{"left":0.5447141,"top":0.27813247,"width":0.0026595744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.54737365,"top":0.27813247,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.3140463,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.31683958,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.33776596,"top":0.3140463,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.31484437,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 2 weeks ago","depth":14,"bounds":{"left":0.3620346,"top":0.31484437,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"bounds":{"left":0.3620346,"top":0.32282522,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"bounds":{"left":0.3620346,"top":0.32282522,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.38181517,"top":0.32282522,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":15,"bounds":{"left":0.40874335,"top":0.32122904,"width":0.026928192,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":17,"bounds":{"left":0.40874335,"top":0.32282522,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No description provided.","depth":18,"bounds":{"left":0.3620346,"top":0.35953712,"width":0.05285904,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.38747007,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"LakyLak","depth":14,"bounds":{"left":0.3700133,"top":0.45291302,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"bounds":{"left":0.3700133,"top":0.45291302,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"bounds":{"left":0.3884641,"top":0.45291302,"width":0.016289894,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"bounds":{"left":0.40475398,"top":0.45291302,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"bounds":{"left":0.40757978,"top":0.45291302,"width":0.020944148,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"bounds":{"left":0.42852393,"top":0.45291302,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"bounds":{"left":0.42852393,"top":0.45291302,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.3700133,"top":0.49281725,"width":0.0066489363,"height":0.015961692},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"bounds":{"left":0.37865692,"top":0.4964086,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"bounds":{"left":0.37865692,"top":0.4964086,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"add AJ reports User pilot tracking","depth":14,"bounds":{"left":0.40009972,"top":0.4964086,"width":0.081615694,"height":0.011572227},"help_text":"JY-20543 add AJ reports User pilot tracking","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"add AJ reports User pilot tracking","depth":15,"bounds":{"left":0.40009972,"top":0.4964086,"width":0.081615694,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"2 / 4 checks OK","depth":14,"bounds":{"left":0.60139626,"top":0.49281725,"width":0.005319149,"height":0.016759777},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"609200a","depth":14,"bounds":{"left":0.6080452,"top":0.4964086,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"609200a","depth":15,"bounds":{"left":0.6080452,"top":0.4964086,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.3700133,"top":0.5343176,"width":0.0066489363,"height":0.015961692},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"bounds":{"left":0.37865692,"top":0.53790903,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"bounds":{"left":0.37865692,"top":0.53790903,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"code smells","depth":14,"bounds":{"left":0.40009972,"top":0.53790903,"width":0.02642952,"height":0.011572227},"help_text":"JY-20543 code smells","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"code smells","depth":15,"bounds":{"left":0.40009972,"top":0.53790903,"width":0.02642952,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"8 / 10 checks OK","depth":14,"bounds":{"left":0.60139626,"top":0.5343176,"width":0.005319149,"height":0.016759777},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"21ab308","depth":14,"bounds":{"left":0.6080452,"top":0.53790903,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21ab308","depth":15,"bounds":{"left":0.6080452,"top":0.53790903,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.3700133,"top":0.56304866,"width":0.0066489363,"height":0.015961692},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"bounds":{"left":0.37865692,"top":0.5666401,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"bounds":{"left":0.37865692,"top":0.5666401,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix test","depth":14,"bounds":{"left":0.40009972,"top":0.5666401,"width":0.019281914,"height":0.011572227},"help_text":"JY-20543 fix test","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix test","depth":15,"bounds":{"left":0.40009972,"top":0.5666401,"width":0.019281914,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 21 checks OK","depth":14,"bounds":{"left":0.60139626,"top":0.56304866,"width":0.005319149,"height":0.016759777},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"93a69eb","depth":14,"bounds":{"left":0.6080452,"top":0.5666401,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"93a69eb","depth":15,"bounds":{"left":0.6080452,"top":0.5666401,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.3700133,"top":0.5917797,"width":0.0066489363,"height":0.015961692},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"bounds":{"left":0.37865692,"top":0.5953711,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"bounds":{"left":0.37865692,"top":0.5953711,"width":0.019115692,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix event","depth":14,"bounds":{"left":0.40009972,"top":0.5953711,"width":0.021609042,"height":0.011572227},"help_text":"JY-20543 fix event","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix event","depth":15,"bounds":{"left":0.40009972,"top":0.5953711,"width":0.021609042,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 22 checks OK","depth":14,"bounds":{"left":0.60139626,"top":0.5917797,"width":0.005319149,"height":0.016759777},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"01241ae","depth":14,"bounds":{"left":0.6080452,"top":0.5953711,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"01241ae","depth":15,"bounds":{"left":0.6080452,"top":0.5953711,"width":0.016954787,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":14,"bounds":{"left":0.3700133,"top":0.64285713,"width":0.0066489363,"height":0.017956903},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"LakyLak","depth":14,"bounds":{"left":0.3778258,"top":0.6444533,"width":0.01861702,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"bounds":{"left":0.3778258,"top":0.6444533,"width":0.01861702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested review from","depth":14,"bounds":{"left":0.39644283,"top":0.6444533,"width":0.05086436,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Vasil-Jiminny","depth":14,"bounds":{"left":0.44730717,"top":0.6444533,"width":0.03025266,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vasil-Jiminny","depth":15,"bounds":{"left":0.44730717,"top":0.6444533,"width":0.03025266,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":14,"bounds":{"left":0.47755983,"top":0.6444533,"width":0.0026595744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":14,"bounds":{"left":0.48021942,"top":0.6444533,"width":0.03706782,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":15,"bounds":{"left":0.48021942,"top":0.6444533,"width":0.03706782,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":14,"bounds":{"left":0.51728725,"top":0.6444533,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"bounds":{"left":0.52792555,"top":0.6444533,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.52792555,"top":0.6444533,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"bounds":{"left":0.5633311,"top":0.64285713,"width":0.026928192,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"bounds":{"left":0.5633311,"top":0.6444533,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":13,"bounds":{"left":0.33776596,"top":0.68994415,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"nikolaybiaivanov","depth":15,"bounds":{"left":0.3700133,"top":0.698324,"width":0.036901597,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":16,"bounds":{"left":0.3700133,"top":0.698324,"width":0.036901597,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"approved these changes","depth":14,"bounds":{"left":0.40824467,"top":0.698324,"width":0.05418883,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"bounds":{"left":0.46243352,"top":0.698324,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"bounds":{"left":0.46243352,"top":0.698324,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View reviewed changes","depth":14,"bounds":{"left":0.5739694,"top":0.69393456,"width":0.051030584,"height":0.022346368},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View reviewed changes","depth":16,"bounds":{"left":0.57696146,"top":0.69912213,"width":0.04504654,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"bounds":{"left":0.33776596,"top":0.7418196,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.7426177,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 2 weeks ago","depth":13,"bounds":{"left":0.3620346,"top":0.7426177,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.3620346,"top":0.75059855,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.75059855,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.39744017,"top":0.75059855,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"bounds":{"left":0.42436835,"top":0.7490024,"width":0.026761968,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"bounds":{"left":0.42436835,"top":0.75059855,"width":0.026761968,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.3620346,"top":0.7873105,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.3620346,"top":0.7873105,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.3620346,"top":0.8152434,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"bounds":{"left":0.37200797,"top":0.8152434,"width":0.013796543,"height":0.0207502},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"bounds":{"left":0.37416887,"top":0.820431,"width":0.004155585,"height":0.012769354},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.38098404,"top":0.820431,"width":0.0018284575,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"bounds":{"left":0.33776596,"top":0.8750998,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.8758978,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 2 weeks ago •","depth":13,"bounds":{"left":0.3620346,"top":0.8758978,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"bounds":{"left":0.3620346,"top":0.8838787,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"bounds":{"left":0.3620346,"top":0.8838787,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"bounds":{"left":0.3804854,"top":0.88547486,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.390625,"top":0.8838787,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"bounds":{"left":0.41738698,"top":0.8822825,"width":0.026928192,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"bounds":{"left":0.41738698,"top":0.8838787,"width":0.026928192,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"bounds":{"left":0.44564494,"top":0.8838787,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"bounds":{"left":0.44896942,"top":0.8822825,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"bounds":{"left":0.44896942,"top":0.8838787,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"bounds":{"left":0.3620346,"top":0.92098963,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"bounds":{"left":0.39810506,"top":0.92098963,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"bounds":{"left":0.39810506,"top":0.92098963,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 11s","depth":18,"bounds":{"left":0.43650267,"top":0.92098963,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"bounds":{"left":0.47257313,"top":0.92098963,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"bounds":{"left":0.48321143,"top":0.92098963,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"bounds":{"left":0.48321143,"top":0.92098963,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.3620346,"top":0.97725457,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.3620346,"top":0.9776536,"width":0.03523936,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.3643617,"top":1.0,"width":0.004654255,"height":-0.011971235},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gather context","depth":18,"bounds":{"left":0.3700133,"top":1.0,"width":0.03324468,"height":-0.010774136},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.3643617,"top":1.0,"width":0.004654255,"height":-0.031923413},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read changed files","depth":18,"bounds":{"left":0.3700133,"top":1.0,"width":0.042220745,"height":-0.030726314},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.3643617,"top":1.0,"width":0.004654255,"height":-0.051875472},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Analyze code and provide review feedback","depth":18,"bounds":{"left":0.3700133,"top":1.0,"width":0.09325133,"height":-0.050678372},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ShouldQueue","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and solid test coverage. A few things worth flagging:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Issues","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Issues","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Silent error swallowing — no logging on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L44–45","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UserPilot tracking failed for automated report","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exception","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getMessage","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"()]);","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Partial retry risk — duplicate tracking on queue retry","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L36–47","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If tracking succeeds for user #1 and fails for user #2 (throwing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), the job is released and retried — which will re-track user #1. For","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exec_summary","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Missing return type hint on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L62","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The return type annotation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is missing (the method has no return type declared). Minor but inconsistent with the","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"discipline in the file.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Minor inefficiency — payload built before users are resolved","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L35–36","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"payload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"resolveUsers","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"buildPayload","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called even when","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"resolveUsers","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or after a guard on the users list.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"What looks good","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"What looks good","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Clean event/listener separation —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReportGenerated","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"event is minimal and focused","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getRecipientUserObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a sensible addition to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReportsService","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and properly typed","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Correct use of","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Constants::QUEUE_DELAYABLE","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for the queue name","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EventDispatcher","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"properly injected in","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ReportController","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and tested","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"yalokin-jiminny","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"reviewed","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View reviewed changes","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View reviewed changes","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Comment thread","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"38","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$payload = $this->buildPayload($automatedReport);","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"39","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"40","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"try {","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"41","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach ($this->resolveUsers($automatedReport) as $user) {","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"38","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"39","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"40","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"41","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$payload = $this->buildPayload($automatedReport);","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"+","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
6978436726275441624
|
8098700914860158633
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
5 days ago
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 2 weeks ago •
claude
claude
bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {
Copy
buildPayload
is called even when
resolveUsers
returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the
foreach
or after a guard on the users list.
What looks good
What looks good
Clean event/listener separation —
AutomatedReportGenerated
event is minimal and focused
getRecipientUserObjects
is a sensible addition to
AutomatedReportsService
and properly typed
Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered
Correct use of
Constants::QUEUE_DELAYABLE
for the queue name
EventDispatcher
properly injected in
ReportController
and tested
Add or remove reactions
yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
reviewed
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
Comment thread
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
38
+
$payload = $this->buildPayload($automatedReport);
39
+
40
+
try {
41
+
foreach ($this->resolveUsers($automatedReport) as $user) {
38
39
40
41
+
$payload = $this->buildPayload($automatedReport);
+...
|
51673
|
|
76442
|
1915
|
17
|
2026-04-24T07:58:41.247239+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-24/1777 /Users/lukas/.screenpipe/data/data/2026-04-24/1777017521247_m1.jpg...
|
Firefox
|
JY-20543 add AJ reports User pilot tracking by Lak JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11932
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app
JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app
New Tab
New Tab
AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app
AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app
JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app
JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app
Jiminny
Jiminny
Userpilot | Nudge-created
Userpilot | Nudge-created
Summary - app in Jiminny SonarQube Cloud
Summary - app in Jiminny SonarQube Cloud
Pipelines - jiminny/app
Pipelines - jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (32)
Security and quality
(
32
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
last week
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude Bot commented 2 weeks ago •
claude
claude
Bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {
Copy
buildPayload
is called even when
resolveUsers
returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the
foreach
or after a guard on the users list.
What looks good
What looks good
Clean event/listener separation —
AutomatedReportGenerated
event is minimal and focused
getRecipientUserObjects
is a sensible addition to
AutomatedReportsService
and properly typed
Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered
Correct use of
Constants::QUEUE_DELAYABLE
for the queue name
EventDispatcher
properly injected in
ReportController
and tested
Add or remove reactions
yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
reviewed
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
Comment thread
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Nudge-created","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Nudge-created","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Summary - app in Jiminny SonarQube Cloud","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summary - app in Jiminny SonarQube Cloud","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.016666668,"top":0.0,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (32)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-20543 add AJ reports User pilot tracking #11932 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20543 add AJ reports User pilot tracking","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11932","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Merged","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"merged 6 commits into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-automated-reports-ask-jiminny","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-automated-reports-ask-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20543-AJ-report-tracking","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543-AJ-report-tracking","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"last week","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lines changed: 284 additions & 1 deletion","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (4)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (6)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (7)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 2 weeks ago","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No description provided.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"add AJ reports User pilot tracking","depth":14,"help_text":"JY-20543 add AJ reports User pilot tracking","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"add AJ reports User pilot tracking","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"2 / 4 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"609200a","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"609200a","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"code smells","depth":14,"help_text":"JY-20543 code smells","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"code smells","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"8 / 10 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"21ab308","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21ab308","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix test","depth":14,"help_text":"JY-20543 fix test","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix test","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 21 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"93a69eb","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"93a69eb","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20543","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20543","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix event","depth":14,"help_text":"JY-20543 fix event","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix event","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"21 / 22 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"01241ae","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"01241ae","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"LakyLak","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested review from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Vasil-Jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vasil-Jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"nikolaybiaivanov","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"nikolaybiaivanov","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"nikolaybiaivanov","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"approved these changes","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View reviewed changes","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View reviewed changes","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 2 weeks ago","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude Bot commented 2 weeks ago •","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bot","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 11s","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.43263888,"top":0.0,"width":0.5375,"height":0.024444444},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.43263888,"top":0.0,"width":0.07361111,"height":0.023333333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.4375,"top":0.016666668,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gather context","depth":18,"bounds":{"left":0.44930556,"top":0.015,"width":0.06944445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.4375,"top":0.044444446,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read changed files","depth":18,"bounds":{"left":0.44930556,"top":0.042777777,"width":0.088194445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"bounds":{"left":0.4375,"top":0.072222225,"width":0.009722223,"height":0.015555556},"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Analyze code and provide review feedback","depth":18,"bounds":{"left":0.44930556,"top":0.07055555,"width":0.19479166,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via","depth":17,"bounds":{"left":0.43263888,"top":0.15333334,"width":0.5138889,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ShouldQueue","depth":18,"bounds":{"left":0.4357639,"top":0.17944445,"width":0.05486111,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and solid test coverage. A few things worth flagging:","depth":17,"bounds":{"left":0.49375,"top":0.17666666,"width":0.24166666,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Issues","depth":16,"bounds":{"left":0.43263888,"top":0.22444445,"width":0.5375,"height":0.019444445},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Issues","depth":17,"bounds":{"left":0.43263888,"top":0.225,"width":0.029861111,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Silent error swallowing — no logging on","depth":18,"bounds":{"left":0.43263888,"top":0.2638889,"width":0.19652778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":19,"bounds":{"left":0.6326389,"top":0.26666668,"width":0.074652776,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.4357639,"top":0.29,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L44–45","depth":17,"bounds":{"left":0.83229166,"top":0.28722224,"width":0.03784722,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"bounds":{"left":0.44375,"top":0.34555554,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"bounds":{"left":0.45347223,"top":0.34555554,"width":0.025,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"bounds":{"left":0.47847223,"top":0.34555554,"width":0.08958333,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.56805557,"top":0.34555554,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.5729167,"top":0.34555554,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"bounds":{"left":0.44375,"top":0.34555554,"width":0.14930555,"height":0.035},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.46354166,"top":0.36444443,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.46840277,"top":0.36444443,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.48854166,"top":0.36444443,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"bounds":{"left":0.4982639,"top":0.36444443,"width":0.035069443,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.53333336,"top":0.36444443,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"bounds":{"left":0.5381944,"top":0.36444443,"width":0.019791666,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"bounds":{"left":0.44375,"top":0.36444443,"width":0.124305554,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.9409722,"top":0.335,"width":0.023611112,"height":0.039444443},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:","depth":17,"bounds":{"left":0.43263888,"top":0.43944445,"width":0.534375,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":17,"bounds":{"left":0.44375,"top":0.52055556,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"catch","depth":17,"bounds":{"left":0.45347223,"top":0.52055556,"width":0.025,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(GuzzleException","depth":17,"bounds":{"left":0.47847223,"top":0.52055556,"width":0.08958333,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.56805557,"top":0.52055556,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.5729167,"top":0.52055556,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"bounds":{"left":0.44375,"top":0.52055556,"width":0.14930555,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.46354166,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.46840277,"top":0.54,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.48854166,"top":0.54,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"bounds":{"left":0.4982639,"top":0.54,"width":0.029861111,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.528125,"top":0.54,"width":0.010069445,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"bounds":{"left":0.5381944,"top":0.54,"width":0.034722224,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.5729167,"top":0.54,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.578125,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UserPilot tracking failed for automated report","depth":17,"bounds":{"left":0.5829861,"top":0.54,"width":0.22881944,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.81180555,"top":0.54,"width":0.0052083335,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"bounds":{"left":0.81701386,"top":0.54,"width":0.014930556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.83194447,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exception","depth":17,"bounds":{"left":0.8368056,"top":0.54,"width":0.044791665,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"bounds":{"left":0.8815972,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"bounds":{"left":0.88645834,"top":0.54,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.9065972,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"e","depth":17,"bounds":{"left":0.9114583,"top":0.54,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.91631943,"top":0.54,"width":0.010069445,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getMessage","depth":17,"bounds":{"left":0.92638886,"top":0.54,"width":0.049652778,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"()]);","depth":17,"bounds":{"left":0.44375,"top":0.54,"width":0.55625,"height":0.035},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.46354166,"top":0.5588889,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"bounds":{"left":0.46840277,"top":0.5588889,"width":0.02013889,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"bounds":{"left":0.48854166,"top":0.5588889,"width":0.009722223,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"release","depth":17,"bounds":{"left":0.4982639,"top":0.5588889,"width":0.035069443,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"bounds":{"left":0.53333336,"top":0.5588889,"width":0.0048611113,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3600","depth":17,"bounds":{"left":0.5381944,"top":0.5588889,"width":0.019791666,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");\n}","depth":17,"bounds":{"left":0.44375,"top":0.5588889,"width":0.124305554,"height":0.035555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.9409722,"top":0.51055557,"width":0.023611112,"height":0.039444443},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"bounds":{"left":0.43263888,"top":0.6338889,"width":0.043055557,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"bounds":{"left":0.43263888,"top":0.6338889,"width":0.043055557,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Partial retry risk — duplicate tracking on queue retry","depth":18,"bounds":{"left":0.43263888,"top":0.71444446,"width":0.25659722,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.4357639,"top":0.7405556,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L36–47","depth":17,"bounds":{"left":0.83229166,"top":0.73777777,"width":0.0375,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If tracking succeeds for user #1 and fails for user #2 (throwing","depth":17,"bounds":{"left":0.43263888,"top":0.7788889,"width":0.28194445,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleException","depth":18,"bounds":{"left":0.71770835,"top":0.7816667,"width":0.074652776,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), the job is released and retried — which will re-track user #1. For","depth":17,"bounds":{"left":0.43263888,"top":0.7788889,"width":0.5170139,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"exec_summary","depth":18,"bounds":{"left":0.57604164,"top":0.805,"width":0.059722222,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.","depth":17,"bounds":{"left":0.43263888,"top":0.80222225,"width":0.51180553,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Missing return type hint on","depth":18,"bounds":{"left":0.43263888,"top":0.9061111,"width":0.13993056,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":19,"bounds":{"left":0.57604164,"top":0.9088889,"width":0.059722222,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"bounds":{"left":0.4357639,"top":0.93222225,"width":0.39305556,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L62","depth":17,"bounds":{"left":0.83229166,"top":0.92944443,"width":0.019791666,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"bounds":{"left":0.44375,"top":0.9872222,"width":0.034722224,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"bounds":{"left":0.48333332,"top":0.9872222,"width":0.039930556,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload(","depth":17,"bounds":{"left":0.5232639,"top":0.9872222,"width":0.06979167,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReport","depth":17,"bounds":{"left":0.59305555,"top":0.9872222,"width":0.074652776,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"bounds":{"left":0.67256945,"top":0.9872222,"width":0.0048611113,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"bounds":{"left":0.67743057,"top":0.9872222,"width":0.074652776,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"bounds":{"left":0.75208336,"top":0.9872222,"width":0.014930556,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":17,"bounds":{"left":0.7670139,"top":0.9872222,"width":0.025,"height":0.012777805},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"bounds":{"left":0.9409722,"top":0.9772222,"width":0.023611112,"height":0.022777796},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The return type annotation","depth":17,"bounds":{"left":0.43263888,"top":1.0,"width":0.12256944,"height":-0.046111107},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"array","depth":18,"bounds":{"left":0.55868053,"top":1.0,"width":0.024652777,"height":-0.04888892},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is missing (the method has no return type declared). Minor but inconsistent with the","depth":17,"bounds":{"left":0.5868056,"top":1.0,"width":0.37986112,"height":-0.046111107},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"bounds":{"left":0.4357639,"top":1.0,"width":0.114583336,"height":-0.07222223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"discipline in the file.","depth":17,"bounds":{"left":0.5534722,"top":1.0,"width":0.09201389,"height":-0.06944442},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Minor inefficiency — payload built before users are resolved","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"L35–36","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"payload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"buildPayload","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"resolveUsers","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"automatedReport","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"buildPayload","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called even when","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"resolveUsers","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"foreach","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or after a guard on the users list.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"What looks good","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"What looks good","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Clean event/listener separation —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReportGenerated","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"event is minimal and focused","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getRecipientUserObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a sensible addition to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AutomatedReportsService","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and properly typed","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Correct use of","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Constants::QUEUE_DELAYABLE","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for the queue name","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EventDispatcher","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"properly injected in","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ReportController","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and tested","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"yalokin-jiminny","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"reviewed","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 weeks ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 weeks ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View reviewed changes","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View reviewed changes","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Comment thread","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-5295920215255345601
|
8097571028145729256
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app
JY-20489 | Optimize Nudges - Phase 2 by yalokin-jiminny · Pull Request #11997 · jiminny/app
New Tab
New Tab
AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app
AI reports promotion pages by nikolay-yankov · Pull Request #11998 · jiminny/app
JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app
JY-9712 | Nuges to expire after one year by nikolaybiaivanov · Pull Request #11981 · jiminny/app
Jiminny
Jiminny
Userpilot | Nudge-created
Userpilot | Nudge-created
Summary - app in Jiminny SonarQube Cloud
Summary - app in Jiminny SonarQube Cloud
Pipelines - jiminny/app
Pipelines - jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (32)
Security and quality
(
32
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20543 add AJ reports User pilot tracking #11932 Edit title
JY-20543 add AJ reports User pilot tracking
#
11932
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 6 commits into
JY-18909-automated-reports-ask-jiminny
JY-18909-automated-reports-ask-jiminny
from
JY-20543-AJ-report-tracking
JY-20543-AJ-report-tracking
Copy head branch name to clipboard
last week
Lines changed: 284 additions & 1 deletion
Conversation (4)
Conversation
(
4
)
Commits (6)
Commits
(
6
)
Checks (3)
Checks
(
3
)
Files changed (7)
Files changed
(
7
)
Conversation
Conversation
@LakyLak
Show options
LakyLak commented 2 weeks ago
LakyLak
LakyLak
commented
2 weeks ago
2 weeks ago
No description provided.
Add or remove reactions
LakyLak
LakyLak
added
4
commits
2 weeks ago
2 weeks ago
@LakyLak
JY-20543
JY-20543
add AJ reports User pilot tracking
add AJ reports User pilot tracking
2 / 4 checks OK
609200a
609200a
@LakyLak
JY-20543
JY-20543
code smells
code smells
8 / 10 checks OK
21ab308
21ab308
@LakyLak
JY-20543
JY-20543
fix test
fix test
21 / 21 checks OK
93a69eb
93a69eb
@LakyLak
JY-20543
JY-20543
fix event
fix event
21 / 22 checks OK
01241ae
01241ae
@LakyLak
LakyLak
LakyLak
requested review from
Vasil-Jiminny
Vasil-Jiminny
,
nikolaybiaivanov
nikolaybiaivanov
and
yalokin-jiminny
yalokin-jiminny
2 weeks ago
2 weeks ago
nikolaybiaivanov
nikolaybiaivanov
nikolaybiaivanov
approved these changes
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
@yalokin-jiminny
Show options
yalokin-jiminny commented 2 weeks ago
yalokin-jiminny
yalokin-jiminny
commented
2 weeks ago
2 weeks ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude Bot commented 2 weeks ago •
claude
claude
Bot
commented
2 weeks ago
2 weeks ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 11s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed files
Completed task
Analyze code and provide review feedback
Overall the implementation is clean and well-structured. Good use of the event/listener pattern, proper queuing via
ShouldQueue
, and solid test coverage. A few things worth flagging:
Issues
Issues
1. Silent error swallowing — no logging on
GuzzleException
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L44–45
}
catch
(GuzzleException
$
e
) {
$
this
->
release
(
3600
);
}
Copy
The exception is caught and silently discarded. If UserPilot API calls start failing, there's no observable signal. Consider logging the error before releasing:
}
catch
(GuzzleException
$
e
) {
$
this
->
logger
->
warning
(
'
UserPilot tracking failed for automated report
'
, [
'
exception
'
=>
$
e
->
getMessage
()]);
$
this
->
release
(
3600
);
}
Copy
Fix this →
Fix this →
2. Partial retry risk — duplicate tracking on queue retry
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L36–47
If tracking succeeds for user #1 and fails for user #2 (throwing
GuzzleException
), the job is released and retried — which will re-track user #1. For
exec_summary
reports with multiple recipients this could cause duplicate UserPilot events. This may be acceptable given the low stakes of analytics data, but worth being aware of.
3. Missing return type hint on
buildPayload
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L62
private
function
buildPayload(
AutomatedReport
$
automatedReport
):
array
Copy
The return type annotation
array
is missing (the method has no return type declared). Minor but inconsistent with the
declare(strict_types=1)
discipline in the file.
Fix this →
Fix this →
4. Minor inefficiency — payload built before users are resolved
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php
L35–36
$
payload
=
$
this
->
buildPayload
(
$
automatedReport
);
foreach
(
$
this
->
resolveUsers
(
$
automatedReport
)
as
$
user
) {
Copy
buildPayload
is called even when
resolveUsers
returns an empty array (e.g. AskJiminny report with null creator). No functional issue, but the payload build could be moved inside the
foreach
or after a guard on the users list.
What looks good
What looks good
Clean event/listener separation —
AutomatedReportGenerated
event is minimal and focused
getRecipientUserObjects
is a sensible addition to
AutomatedReportsService
and properly typed
Test coverage is thorough: token-null guard, AJ creator present/absent, multi-recipient exec report, empty recipients, and Guzzle failure — all covered
Correct use of
Constants::QUEUE_DELAYABLE
for the queue name
EventDispatcher
properly injected in
ReportController
and tested
Add or remove reactions
yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
reviewed
2 weeks ago
2 weeks ago
View reviewed changes
View reviewed changes
Comment thread
app/Listeners/AutomatedReports/UserPilot/TrackAutomatedReportGeneratedEvent.php...
|
NULL
|
|
21058
|
465
|
5
|
2026-04-15T09:48:59.165687+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-15/1776 /Users/lukas/.screenpipe/data/data/2026-04-15/1776246539165_m1.jpg...
|
Firefox
|
JY-18909: Print company logo debugging information JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet — Work...
|
True
|
github.com/jiminny/prophet/pull/472#pullrequestrev github.com/jiminny/prophet/pull/472#pullrequestreview-4112493755...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,552) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Jiminny
Jiminny
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
Close tab
Welcome to Steam
Welcome to Steam
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
prophet
prophet
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Issues
Issues
Pull requests (6)
Pull requests
(
6
)
Agents
Agents
Actions
Actions
Projects
Projects
Wiki
Wiki
Security and quality (20)
Security and quality
(
20
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-18909: Print company logo debugging information #472 Edit title
JY-18909: Print company logo debugging information
#
472
Edit title
Ready to merge
Ready to merge
Code
Code
Open
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Lines changed: 17 additions & 0 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Open
JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
#
472
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Conversation
Conversation
@steliyan-g
Show options
steliyan-g commented 22 minutes ago
steliyan-g
steliyan-g
commented
22 minutes ago
22 minutes ago
No description provided.
Add or remove reactions
@steliyan-g
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
3 / 4 checks OK
2ad20d6
2ad20d6
@steliyan-g
Show options
Author
steliyan-g commented 21 minutes ago
steliyan-g
steliyan-g
commented
21 minutes ago
21 minutes ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 21 minutes ago •
claude
claude
bot
commented
21 minutes ago
21 minutes ago
•
edited
edited
Claude finished
@steliyan-g
@steliyan-g
's task in 45s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed file
Completed task
Review CLAUDE.md (not present)
Completed task
Post review feedback
This PR adds debug logging statements across the company logo rendering pipeline in
async_aa_report_processor.py
. The changes are straightforward and the logs look correct overall. A few observations:
Looks good:
Logging at each meaningful step (
_get_team_info
, S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.
Using
%s
/
%d
lazy formatting (rather than f-strings) is correct for
logger.info
/
logger.warning
calls — avoids unnecessary string interpolation when the log level is disabled.
The
logger.warning
for unknown MIME type is appropriate severity.
transparent_photo_path or "<not set>"
is a nice touch for readability in logs.
Minor issues:
Inconsistent log format
— Line 101 uses an f-string (
f"Building AA report for request_id: {async_request.request_id}"
), and line 115 also uses f-strings, while the new log lines added in this PR correctly use
%
-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.
encoded_size
can be misleading
(line 302) —
len(b64)
is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log
len(image_bytes)
as
image_size_bytes
instead, or label this explicitly as
b64_size
.
Fix this →
Fix this →
No log after
_get_logo_url
returns
— After calling
_get_logo_url
, the result is used in
_generate_pdf_version
but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside
_get_logo_url
cover the internals, but a single line like
"Logo URL resolved: present=%s"
at the
_process_report_request
level would make log correlation easier. (Minor/optional)
Overall the change is solid and achieves the debugging goal. The
encoded_size
point is the only one worth addressing before merge.
Add or remove reactions
@sonarqubecloud
Show options
sonarqubecloud bot commented 14 minutes ago
sonarqubecloud...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,552) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"For you - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Welcome to Steam","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Steam","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"prophet","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"prophet","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Issues","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (6)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Projects","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Projects","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (20)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-18909: Print company logo debugging information #472 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"472","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-debug-company-logo","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-debug-company-logo","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 17 additions & 0 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (5)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909: Print company logo debugging information","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"472","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-debug-company-logo","depth":19,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-debug-company-logo","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"steliyan-g commented 22 minutes ago","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"22 minutes ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 minutes ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No description provided.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@steliyan-g","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-18909: Print company logo debugging information","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"3 / 4 checks OK","depth":13,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"2ad20d6","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2ad20d6","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"steliyan-g commented 21 minutes ago","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"21 minutes ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21 minutes ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 21 minutes ago •","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"21 minutes ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21 minutes ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@steliyan-g","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 45s","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gather context","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read changed file","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review CLAUDE.md (not present)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Post review feedback","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR adds debug logging statements across the company logo rendering pipeline in","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"async_aa_report_processor.py","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". The changes are straightforward and the logs look correct overall. A few observations:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Looks good:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logging at each meaningful step (","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_team_info","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Using","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%d","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lazy formatting (rather than f-strings) is correct for","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.info","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.warning","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"calls — avoids unnecessary string interpolation when the log level is disabled.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.warning","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for unknown MIME type is appropriate severity.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"transparent_photo_path or \"<not set>\"","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a nice touch for readability in logs.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor issues:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Inconsistent log format","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— Line 101 uses an f-string (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"f\"Building AA report for request_id: {async_request.request_id}\"","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), and line 115 also uses f-strings, while the new log lines added in this PR correctly use","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"encoded_size","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be misleading","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(line 302) —","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"len(b64)","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"len(image_bytes)","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"image_size_bytes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead, or label this explicitly as","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"b64_size","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":19,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No log after","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"returns","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— After calling","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", the result is used in","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_generate_pdf_version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cover the internals, but a single line like","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"Logo URL resolved: present=%s\"","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"at the","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_process_report_request","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"level would make log correlation easier. (Minor/optional)","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the change is solid and achieves the debugging goal. The","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"encoded_size","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"point is the only one worth addressing before merge.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@sonarqubecloud","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"sonarqubecloud bot commented 14 minutes ago","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"sonarqubecloud","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
5195211248530594820
|
8093663406763961753
|
click
|
accessibility
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,552) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Jiminny
Jiminny
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
Close tab
Welcome to Steam
Welcome to Steam
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
prophet
prophet
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Issues
Issues
Pull requests (6)
Pull requests
(
6
)
Agents
Agents
Actions
Actions
Projects
Projects
Wiki
Wiki
Security and quality (20)
Security and quality
(
20
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-18909: Print company logo debugging information #472 Edit title
JY-18909: Print company logo debugging information
#
472
Edit title
Ready to merge
Ready to merge
Code
Code
Open
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Lines changed: 17 additions & 0 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Open
JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
#
472
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Conversation
Conversation
@steliyan-g
Show options
steliyan-g commented 22 minutes ago
steliyan-g
steliyan-g
commented
22 minutes ago
22 minutes ago
No description provided.
Add or remove reactions
@steliyan-g
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
3 / 4 checks OK
2ad20d6
2ad20d6
@steliyan-g
Show options
Author
steliyan-g commented 21 minutes ago
steliyan-g
steliyan-g
commented
21 minutes ago
21 minutes ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 21 minutes ago •
claude
claude
bot
commented
21 minutes ago
21 minutes ago
•
edited
edited
Claude finished
@steliyan-g
@steliyan-g
's task in 45s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed file
Completed task
Review CLAUDE.md (not present)
Completed task
Post review feedback
This PR adds debug logging statements across the company logo rendering pipeline in
async_aa_report_processor.py
. The changes are straightforward and the logs look correct overall. A few observations:
Looks good:
Logging at each meaningful step (
_get_team_info
, S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.
Using
%s
/
%d
lazy formatting (rather than f-strings) is correct for
logger.info
/
logger.warning
calls — avoids unnecessary string interpolation when the log level is disabled.
The
logger.warning
for unknown MIME type is appropriate severity.
transparent_photo_path or "<not set>"
is a nice touch for readability in logs.
Minor issues:
Inconsistent log format
— Line 101 uses an f-string (
f"Building AA report for request_id: {async_request.request_id}"
), and line 115 also uses f-strings, while the new log lines added in this PR correctly use
%
-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.
encoded_size
can be misleading
(line 302) —
len(b64)
is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log
len(image_bytes)
as
image_size_bytes
instead, or label this explicitly as
b64_size
.
Fix this →
Fix this →
No log after
_get_logo_url
returns
— After calling
_get_logo_url
, the result is used in
_generate_pdf_version
but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside
_get_logo_url
cover the internals, but a single line like
"Logo URL resolved: present=%s"
at the
_process_report_request
level would make log correlation easier. (Minor/optional)
Overall the change is solid and achieves the debugging goal. The
encoded_size
point is the only one worth addressing before merge.
Add or remove reactions
@sonarqubecloud
Show options
sonarqubecloud bot commented 14 minutes ago
sonarqubecloud...
|
21056
|
|
21059
|
466
|
11
|
2026-04-15T09:48:59.165662+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-15/1776 /Users/lukas/.screenpipe/data/data/2026-04-15/1776246539165_m2.jpg...
|
Firefox
|
JY-18909: Print company logo debugging information JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet — Work...
|
True
|
github.com/jiminny/prophet/pull/472#pullrequestrev github.com/jiminny/prophet/pull/472#pullrequestreview-4112493755...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,552) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Jiminny
Jiminny
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
Close tab
Welcome to Steam
Welcome to Steam
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
prophet
prophet
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Issues
Issues
Pull requests (6)
Pull requests
(
6
)
Agents
Agents
Actions
Actions
Projects
Projects
Wiki
Wiki
Security and quality (20)
Security and quality
(
20
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-18909: Print company logo debugging information #472 Edit title
JY-18909: Print company logo debugging information
#
472
Edit title
Ready to merge
Ready to merge
Code
Code
Open
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Lines changed: 17 additions & 0 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Open
JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
#
472
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Conversation
Conversation
@steliyan-g
Show options
steliyan-g commented 22 minutes ago
steliyan-g
steliyan-g
commented
22 minutes ago
22 minutes ago
No description provided.
Add or remove reactions
@steliyan-g
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
3 / 4 checks OK
2ad20d6
2ad20d6
@steliyan-g
Show options
Author
steliyan-g commented 21 minutes ago
steliyan-g
steliyan-g
commented
21 minutes ago
21 minutes ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 21 minutes ago •
claude
claude
bot
commented
21 minutes ago
21 minutes ago
•
edited
edited
Claude finished
@steliyan-g
@steliyan-g
's task in 45s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed file
Completed task
Review CLAUDE.md (not present)
Completed task
Post review feedback
This PR adds debug logging statements across the company logo rendering pipeline in
async_aa_report_processor.py
. The changes are straightforward and the logs look correct overall. A few observations:
Looks good:
Logging at each meaningful step (
_get_team_info
, S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.
Using
%s
/
%d
lazy formatting (rather than f-strings) is correct for
logger.info
/
logger.warning
calls — avoids unnecessary string interpolation when the log level is disabled.
The
logger.warning
for unknown MIME type is appropriate severity.
transparent_photo_path or "<not set>"
is a nice touch for readability in logs.
Minor issues:
Inconsistent log format
— Line 101 uses an f-string (
f"Building AA report for request_id: {async_request.request_id}"
), and line 115 also uses f-strings, while the new log lines added in this PR correctly use
%
-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.
encoded_size
can be misleading
(line 302) —
len(b64)
is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log
len(image_bytes)
as
image_size_bytes
instead, or label this explicitly as
b64_size
.
Fix this →
Fix this →
No log after
_get_logo_url
returns
— After calling
_get_logo_url
, the result is used in
_generate_pdf_version
but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside
_get_logo_url
cover the internals, but a single line like
"Logo URL resolved: present=%s"
at the
_process_report_request
level would make log correlation easier. (Minor/optional)
Overall the change is solid and achieves the debugging goal. The
encoded_size
point is the only one worth addressing before merge.
Add or remove reactions
@sonarqubecloud
Show options
sonarqubecloud bot commented 14 minutes ago
sonarqubecloud...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.017578125,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.019921875,"top":0.045138888,"width":0.01796875,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app","depth":4,"bounds":{"left":0.037890624,"top":0.045138888,"width":0.01796875,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.055859376,"top":0.045138888,"width":0.017578125,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0734375,"top":0.045138888,"width":0.01796875,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,552) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.00234375,"top":0.07361111,"width":0.017578125,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"For you - Confluence","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.04296875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.049609374,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.07304688,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.01875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.24101563,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.21367188,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.28819445,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Welcome to Steam","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Steam","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.03828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"prophet","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"prophet","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Issues","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (6)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Projects","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Projects","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (20)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-18909: Print company logo debugging information #472 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"472","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-debug-company-logo","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-debug-company-logo","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 17 additions & 0 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (5)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"bounds":{"left":0.3191406,"top":0.063194446,"width":0.0140625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard","depth":14,"bounds":{"left":0.34101564,"top":0.050694443,"width":0.22304687,"height":0.036805555},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909: Print company logo debugging information","depth":16,"bounds":{"left":0.34101564,"top":0.050694443,"width":0.14179687,"height":0.017361112},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":17,"bounds":{"left":0.34101564,"top":0.05486111,"width":0.14179687,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"bounds":{"left":0.4859375,"top":0.05486111,"width":0.003515625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"472","depth":16,"bounds":{"left":0.48945314,"top":0.05486111,"width":0.009765625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":18,"bounds":{"left":0.34101564,"top":0.072916664,"width":0.023046875,"height":0.010416667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":19,"bounds":{"left":0.34101564,"top":0.072916664,"width":0.023046875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":18,"bounds":{"left":0.365625,"top":0.072916664,"width":0.06523438,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"bounds":{"left":0.43242186,"top":0.07152778,"width":0.021484375,"height":0.013194445},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"bounds":{"left":0.43476564,"top":0.072916664,"width":0.016796876,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"bounds":{"left":0.45546874,"top":0.072916664,"width":0.01015625,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-18909-debug-company-logo","depth":19,"bounds":{"left":0.4671875,"top":0.07152778,"width":0.08085938,"height":0.013194445},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909-debug-company-logo","depth":20,"bounds":{"left":0.46953124,"top":0.072916664,"width":0.076171875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"bounds":{"left":0.54960936,"top":0.068055555,"width":0.0109375,"height":0.019444445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"steliyan-g commented 22 minutes ago","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"22 minutes ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"22 minutes ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No description provided.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@steliyan-g","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-18909: Print company logo debugging information","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-18909: Print company logo debugging information","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"3 / 4 checks OK","depth":13,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"2ad20d6","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2ad20d6","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"steliyan-g commented 21 minutes ago","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"steliyan-g","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"steliyan-g","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"21 minutes ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21 minutes ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 21 minutes ago •","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"21 minutes ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"21 minutes ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@steliyan-g","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@steliyan-g","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 45s","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gather context","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read changed file","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review CLAUDE.md (not present)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Completed task","depth":18,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Post review feedback","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR adds debug logging statements across the company logo rendering pipeline in","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"async_aa_report_processor.py","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". The changes are straightforward and the logs look correct overall. A few observations:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Looks good:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logging at each meaningful step (","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_team_info","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Using","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%s","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%d","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lazy formatting (rather than f-strings) is correct for","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.info","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.warning","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"calls — avoids unnecessary string interpolation when the log level is disabled.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger.warning","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for unknown MIME type is appropriate severity.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"transparent_photo_path or \"<not set>\"","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a nice touch for readability in logs.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor issues:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Inconsistent log format","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— Line 101 uses an f-string (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"f\"Building AA report for request_id: {async_request.request_id}\"","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), and line 115 also uses f-strings, while the new log lines added in this PR correctly use","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"%","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"encoded_size","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be misleading","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(line 302) —","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"len(b64)","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"len(image_bytes)","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"image_size_bytes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead, or label this explicitly as","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"b64_size","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":19,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No log after","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"returns","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— After calling","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", the result is used in","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_generate_pdf_version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_get_logo_url","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cover the internals, but a single line like","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"Logo URL resolved: present=%s\"","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"at the","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"_process_report_request","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"level would make log correlation easier. (Minor/optional)","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the change is solid and achieves the debugging goal. The","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"encoded_size","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"point is the only one worth addressing before merge.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@sonarqubecloud","depth":13,"bounds":{"left":0.31289062,"top":0.0,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.634375,"top":0.0,"width":0.009375,"height":0.025694445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"sonarqubecloud bot commented 14 minutes ago","depth":13,"bounds":{"left":0.34140626,"top":0.0,"width":0.28359374,"height":0.025694445},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"sonarqubecloud","depth":15,"bounds":{"left":0.34140626,"top":0.0,"width":0.042578124,"height":0.011805556},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
5195211248530594820
|
8093663406763961753
|
click
|
accessibility
|
NULL
|
Platform Team - Backlog - Jira
Service-Desk - Queu Platform Team - Backlog - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20543 add AJ reports User pilot tracking by LakyLak · Pull Request #11932 · jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Inbox (1,552) - [EMAIL] - Jiminny Mail
For you - Confluence
For you - Confluence
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot
Userpilot
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Jiminny
Jiminny
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
JY-18909: Print company logo debugging information by steliyan-g · Pull Request #472 · jiminny/prophet
Close tab
Welcome to Steam
Welcome to Steam
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
prophet
prophet
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Issues
Issues
Pull requests (6)
Pull requests
(
6
)
Agents
Agents
Actions
Actions
Projects
Projects
Wiki
Wiki
Security and quality (20)
Security and quality
(
20
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-18909: Print company logo debugging information #472 Edit title
JY-18909: Print company logo debugging information
#
472
Edit title
Ready to merge
Ready to merge
Code
Code
Open
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Lines changed: 17 additions & 0 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Open
JY-18909: Print company logo debugging information #472 steliyan-g wants to merge 1 commit into master from JY-18909-debug-company-logo Copy head branch name to clipboard
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
#
472
steliyan-g
steliyan-g
wants to merge 1 commit into
master
master
from
JY-18909-debug-company-logo
JY-18909-debug-company-logo
Copy head branch name to clipboard
Conversation
Conversation
@steliyan-g
Show options
steliyan-g commented 22 minutes ago
steliyan-g
steliyan-g
commented
22 minutes ago
22 minutes ago
No description provided.
Add or remove reactions
@steliyan-g
JY-18909: Print company logo debugging information
JY-18909: Print company logo debugging information
3 / 4 checks OK
2ad20d6
2ad20d6
@steliyan-g
Show options
Author
steliyan-g commented 21 minutes ago
steliyan-g
steliyan-g
commented
21 minutes ago
21 minutes ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 21 minutes ago •
claude
claude
bot
commented
21 minutes ago
21 minutes ago
•
edited
edited
Claude finished
@steliyan-g
@steliyan-g
's task in 45s
——
View job
View job
Code Review
Code Review
Completed task
Gather context
Completed task
Read changed file
Completed task
Review CLAUDE.md (not present)
Completed task
Post review feedback
This PR adds debug logging statements across the company logo rendering pipeline in
async_aa_report_processor.py
. The changes are straightforward and the logs look correct overall. A few observations:
Looks good:
Logging at each meaningful step (
_get_team_info
, S3 fetch, MIME type detection, PDF template rendering) gives good visibility into where failures occur.
Using
%s
/
%d
lazy formatting (rather than f-strings) is correct for
logger.info
/
logger.warning
calls — avoids unnecessary string interpolation when the log level is disabled.
The
logger.warning
for unknown MIME type is appropriate severity.
transparent_photo_path or "<not set>"
is a nice touch for readability in logs.
Minor issues:
Inconsistent log format
— Line 101 uses an f-string (
f"Building AA report for request_id: {async_request.request_id}"
), and line 115 also uses f-strings, while the new log lines added in this PR correctly use
%
-style formatting. It's a pre-existing inconsistency, but worth noting for a future cleanup pass.
encoded_size
can be misleading
(line 302) —
len(b64)
is the size of the base64-encoded string, not the raw image size. The base64 representation is ~33% larger than the actual image bytes. It might be clearer to log
len(image_bytes)
as
image_size_bytes
instead, or label this explicitly as
b64_size
.
Fix this →
Fix this →
No log after
_get_logo_url
returns
— After calling
_get_logo_url
, the result is used in
_generate_pdf_version
but there's no log at the call site indicating whether a logo URL was ultimately produced (empty string vs. data URI). The existing logs inside
_get_logo_url
cover the internals, but a single line like
"Logo URL resolved: present=%s"
at the
_process_report_request
level would make log correlation easier. (Minor/optional)
Overall the change is solid and achieves the debugging goal. The
encoded_size
point is the only one worth addressing before merge.
Add or remove reactions
@sonarqubecloud
Show options
sonarqubecloud bot commented 14 minutes ago
sonarqubecloud...
|
21057
|
|
60920
|
1313
|
43
|
2026-04-21T06:25:20.886623+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776752720886_m2.jpg...
|
Firefox
|
JY-20701 | Reschedule HubSpot Sync Objects by yalo JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11989
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny MCP Connector - Product - Confluence","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny MCP Connector - Product - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.08294548,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.15791224,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.02144282,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.08610372,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.042719416,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.013131649,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.1740359,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.054521278,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.004986702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.16323139,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.16988032,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18085106,"top":0.10574621,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20162898,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21276596,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23454122,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24534574,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (21)","depth":12,"bounds":{"left":0.2601396,"top":0.09936153,"width":0.069980055,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27194148,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31831783,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21","depth":14,"bounds":{"left":0.32130983,"top":0.113727055,"width":0.0048204786,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.32613033,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33277926,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34391624,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.36668882,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.3778258,"top":0.10574621,"width":0.01761968,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.09325133,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.34973404,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.48454124,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.98636967,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Review requested","depth":15,"bounds":{"left":0.35006648,"top":0.2019154,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Review requested","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.039893616,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested your review on this pull request.","depth":15,"bounds":{"left":0.3854721,"top":0.20351157,"width":0.09158909,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add your review","depth":14,"bounds":{"left":0.70212764,"top":0.19872306,"width":0.036901597,"height":0.022346368},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add your review","depth":16,"bounds":{"left":0.70511967,"top":0.20391062,"width":0.030917553,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.24221867,"width":0.26230052,"height":0.031923383},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects","depth":14,"bounds":{"left":0.33776596,"top":0.24301676,"width":0.2122673,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.55269283,"top":0.24301676,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11989","depth":15,"bounds":{"left":0.55917555,"top":0.24301676,"width":0.028922873,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.5894282,"top":0.24541101,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"bounds":{"left":0.6569149,"top":0.24860336,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"bounds":{"left":0.66921544,"top":0.254589,"width":0.038896278,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.24860336,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.254589,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.34840426,"top":0.28651237,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.36702126,"top":0.28332004,"width":0.034075797,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.36702126,"top":0.2849162,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 22 commits into","depth":15,"bounds":{"left":0.40242687,"top":0.2849162,"width":0.069148935,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.47290558,"top":0.282921,"width":0.018284574,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.47490028,"top":0.28611332,"width":0.014295213,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.49251994,"top":0.2849162,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701-reschedule-HubSpot-processing","depth":16,"bounds":{"left":0.50382316,"top":0.282921,"width":0.09524601,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":17,"bounds":{"left":0.50581783,"top":0.28611332,"width":0.09125665,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.60039896,"top":0.28052673,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 949 additions & 97 deletions","depth":14,"bounds":{"left":0.7067819,"top":0.3367917,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (5)","depth":16,"bounds":{"left":0.33776596,"top":0.31883478,"width":0.057347074,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35139626,"top":0.32841182,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38946143,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"bounds":{"left":0.39245346,"top":0.32841182,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.39527926,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (22)","depth":16,"bounds":{"left":0.39511302,"top":0.31883478,"width":0.05069814,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.40874335,"top":0.32841182,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4401596,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":18,"bounds":{"left":0.4431516,"top":0.32841182,"width":0.005485372,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.44863698,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"bounds":{"left":0.44581118,"top":0.31883478,"width":0.04504654,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.45944148,"top":0.32841182,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.48520613,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.48819813,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.49119017,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (11)","depth":16,"bounds":{"left":0.49085772,"top":0.31883478,"width":0.060339097,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.50448805,"top":0.32841182,"width":0.029753989,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.5455452,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11","depth":18,"bounds":{"left":0.54853725,"top":0.32841182,"width":0.0043218085,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.55285907,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.36711892,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.3651237,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago •","depth":14,"bounds":{"left":0.3620346,"top":0.3651237,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":17,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.39744017,"top":0.37310454,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":15,"bounds":{"left":0.42436835,"top":0.3715084,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":17,"bounds":{"left":0.42436835,"top":0.37310454,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"bounds":{"left":0.44930187,"top":0.37310454,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"bounds":{"left":0.45262632,"top":0.3715084,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"bounds":{"left":0.45262632,"top":0.37310454,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20701","depth":16,"bounds":{"left":0.3620346,"top":0.40822026,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"bounds":{"left":0.3620346,"top":0.4086193,"width":0.015791224,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701","depth":17,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":18,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Casus:","depth":16,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Casus:","depth":17,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.015292553,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"We want more fluent curve of the jobs distribution (no spikes) in","depth":18,"bounds":{"left":0.3700133,"top":0.47326416,"width":0.13879654,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm-sync","depth":19,"bounds":{"left":0.51047206,"top":0.47525936,"width":0.018949468,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"queue","depth":18,"bounds":{"left":0.53108376,"top":0.47326416,"width":0.01462766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times","depth":18,"bounds":{"left":0.3700133,"top":0.49281725,"width":0.24750665,"height":0.030726258},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.021110373,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Remove HubSpot from SyncObjects","depth":18,"bounds":{"left":0.3700133,"top":0.5726257,"width":0.07712766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Create separate HubSpotSyncObjects that runs each 5 min instead of each 30","depth":18,"bounds":{"left":0.3700133,"top":0.59217876,"width":0.16855054,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Extract common code","depth":18,"bounds":{"left":0.3700133,"top":0.6121309,"width":0.047041222,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Improve processing Webhooks jobs distribution","depth":18,"bounds":{"left":0.3700133,"top":0.63168395,"width":0.101894945,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Optimize ImportOpportunityBatch:","depth":18,"bounds":{"left":0.3700133,"top":0.65163606,"width":0.07396942,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cache OpportunitySyncableFields, OwnerProfiles, RecordType","depth":20,"bounds":{"left":0.37799203,"top":0.6683959,"width":0.13347739,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"remove findContactByConfigurationAndId - Contact ID already exist","depth":20,"bounds":{"left":0.37799203,"top":0.68834794,"width":0.1456117,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"add timings log","depth":20,"bounds":{"left":0.37799203,"top":0.70790106,"width":0.032912236,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.73623306,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"bounds":{"left":0.33776596,"top":0.7960894,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.79688746,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"bounds":{"left":0.5934175,"top":0.80606544,"width":0.012965426,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago","depth":13,"bounds":{"left":0.3620346,"top":0.79688746,"width":0.22240691,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.39744017,"top":0.80486834,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.42436835,"top":0.8032721,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.42436835,"top":0.80486834,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.3620346,"top":0.86951315,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"bounds":{"left":0.37200797,"top":0.86951315,"width":0.013796543,"height":0.0207502},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"bounds":{"left":0.37416887,"top":0.8747007,"width":0.004155585,"height":0.012769354},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.38098404,"top":0.8747007,"width":0.0018284575,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"bounds":{"left":0.33776596,"top":0.9293695,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.9301676,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 4 days ago •","depth":13,"bounds":{"left":0.3620346,"top":0.9301676,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"bounds":{"left":0.3804854,"top":0.9397446,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.390625,"top":0.93814844,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.41738698,"top":0.9365523,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.41738698,"top":0.93814844,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"bounds":{"left":0.44232047,"top":0.93814844,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"bounds":{"left":0.44581118,"top":0.9365523,"width":0.020113032,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"bounds":{"left":0.44581118,"top":0.93814844,"width":0.014793883,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"bounds":{"left":0.3620346,"top":0.97525936,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 59s","depth":18,"bounds":{"left":0.43650267,"top":0.97525936,"width":0.03756649,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"bounds":{"left":0.47406915,"top":0.97525936,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.3620346,"top":1.0,"width":0.25731382,"height":-0.0315243},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.03523936,"height":-0.031923413},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the approach is clean — extracting the shared logic into","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.13696809,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsCommandTrait","depth":18,"bounds":{"left":0.5006649,"top":1.0,"width":0.0546875,"height":-0.065043926},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a good call, and the scheduling design (offset cron at","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.24335106,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":01","depth":18,"bounds":{"left":0.43567154,"top":1.0,"width":0.0071476065,"height":-0.08180368},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to avoid spikes) makes sense. I found two bugs and a few minor points.","depth":17,"bounds":{"left":0.44448137,"top":1.0,"width":0.15425532,"height":-0.07980847},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Bugs","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bugs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Null dereference on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:49-51","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"$team->crm","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be null if the team has no CRM configuration record. Accessing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->provider","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on null will throw a fatal error. The existing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job handles this by using","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"??","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig->provider ?? 'unknown'","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). The new job needs a null guard here:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"===","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"||","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[SyncHubspotObjects] Team has no HubSpot CRM config","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]);","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$lastSyncedAt","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is nullable but","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requires","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:85,88,116","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"last_synced_at","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// can be null on first run","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// ...","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HubspotInterface","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"void","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"With","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", passing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-typed parameter throws a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TypeError","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". A team that has never synced before will crash on the first run. The parameter should be","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"?Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(and","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncOpportunities","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should handle null), or provide a fallback like","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon::createFromTimestamp(0)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Minor Issues","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor Issues","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Double call to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix()","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() ?","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() .","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"''","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"getLogPrefix()","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called twice. Trivial but unnecessary — capture the result in a variable first.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"command doesn't guard HubSpot when","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{team}","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"argument is given —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/SyncObjects.php:40-41","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teams","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[] = Team::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"idOrUuId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// no provider check","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Running","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-objects <hubspot-team-id>","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dispatches a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsJob","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Design Notes","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Design Notes","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Scheduler placement","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is placed inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"scheduleEveryFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but uses a manual cron","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8362801654720630257
|
8092255885110476233
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'...
|
60918
|
|
60916
|
1313
|
41
|
2026-04-21T06:25:18.644677+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776752718644_m2.jpg...
|
Firefox
|
JY-20701 | Reschedule HubSpot Sync Objects by yalo JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11989
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny MCP Connector - Product - Confluence","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny MCP Connector - Product - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.08294548,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.15791224,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.02144282,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.08610372,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.042719416,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.013131649,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.1740359,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.054521278,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.004986702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.16323139,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.16988032,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18085106,"top":0.10574621,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20162898,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21276596,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23454122,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24534574,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (21)","depth":12,"bounds":{"left":0.2601396,"top":0.09936153,"width":0.069980055,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27194148,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31831783,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21","depth":14,"bounds":{"left":0.32130983,"top":0.113727055,"width":0.0048204786,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.32613033,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33277926,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34391624,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.36668882,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.3778258,"top":0.10574621,"width":0.01761968,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.09325133,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.34973404,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.48454124,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.98636967,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Review requested","depth":15,"bounds":{"left":0.35006648,"top":0.2019154,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Review requested","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.039893616,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested your review on this pull request.","depth":15,"bounds":{"left":0.3854721,"top":0.20351157,"width":0.09158909,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add your review","depth":14,"bounds":{"left":0.70212764,"top":0.19872306,"width":0.036901597,"height":0.022346368},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add your review","depth":16,"bounds":{"left":0.70511967,"top":0.20391062,"width":0.030917553,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.24221867,"width":0.26230052,"height":0.031923383},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects","depth":14,"bounds":{"left":0.33776596,"top":0.24301676,"width":0.2122673,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.55269283,"top":0.24301676,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11989","depth":15,"bounds":{"left":0.55917555,"top":0.24301676,"width":0.028922873,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.5894282,"top":0.24541101,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"bounds":{"left":0.6569149,"top":0.24860336,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"bounds":{"left":0.66921544,"top":0.254589,"width":0.038896278,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.24860336,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.254589,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.34840426,"top":0.28651237,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.36702126,"top":0.28332004,"width":0.034075797,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.36702126,"top":0.2849162,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 22 commits into","depth":15,"bounds":{"left":0.40242687,"top":0.2849162,"width":0.069148935,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.47290558,"top":0.282921,"width":0.018284574,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.47490028,"top":0.28611332,"width":0.014295213,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.49251994,"top":0.2849162,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701-reschedule-HubSpot-processing","depth":16,"bounds":{"left":0.50382316,"top":0.282921,"width":0.09524601,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":17,"bounds":{"left":0.50581783,"top":0.28611332,"width":0.09125665,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.60039896,"top":0.28052673,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 949 additions & 97 deletions","depth":14,"bounds":{"left":0.7067819,"top":0.3367917,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (5)","depth":16,"bounds":{"left":0.33776596,"top":0.31883478,"width":0.057347074,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35139626,"top":0.32841182,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38946143,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"bounds":{"left":0.39245346,"top":0.32841182,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.39527926,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (22)","depth":16,"bounds":{"left":0.39511302,"top":0.31883478,"width":0.05069814,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.40874335,"top":0.32841182,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4401596,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":18,"bounds":{"left":0.4431516,"top":0.32841182,"width":0.005485372,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.44863698,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"bounds":{"left":0.44581118,"top":0.31883478,"width":0.04504654,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.45944148,"top":0.32841182,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.48520613,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.48819813,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.49119017,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (11)","depth":16,"bounds":{"left":0.49085772,"top":0.31883478,"width":0.060339097,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.50448805,"top":0.32841182,"width":0.029753989,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.5455452,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11","depth":18,"bounds":{"left":0.54853725,"top":0.32841182,"width":0.0043218085,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.55285907,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.36711892,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.3651237,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago •","depth":14,"bounds":{"left":0.3620346,"top":0.3651237,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":17,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.39744017,"top":0.37310454,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":15,"bounds":{"left":0.42436835,"top":0.3715084,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":17,"bounds":{"left":0.42436835,"top":0.37310454,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"bounds":{"left":0.44930187,"top":0.37310454,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"bounds":{"left":0.45262632,"top":0.3715084,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"bounds":{"left":0.45262632,"top":0.37310454,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20701","depth":16,"bounds":{"left":0.3620346,"top":0.40822026,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"bounds":{"left":0.3620346,"top":0.4086193,"width":0.015791224,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701","depth":17,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":18,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Casus:","depth":16,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Casus:","depth":17,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.015292553,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"We want more fluent curve of the jobs distribution (no spikes) in","depth":18,"bounds":{"left":0.3700133,"top":0.47326416,"width":0.13879654,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm-sync","depth":19,"bounds":{"left":0.51047206,"top":0.47525936,"width":0.018949468,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"queue","depth":18,"bounds":{"left":0.53108376,"top":0.47326416,"width":0.01462766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times","depth":18,"bounds":{"left":0.3700133,"top":0.49281725,"width":0.24750665,"height":0.030726258},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.021110373,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Remove HubSpot from SyncObjects","depth":18,"bounds":{"left":0.3700133,"top":0.5726257,"width":0.07712766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Create separate HubSpotSyncObjects that runs each 5 min instead of each 30","depth":18,"bounds":{"left":0.3700133,"top":0.59217876,"width":0.16855054,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Extract common code","depth":18,"bounds":{"left":0.3700133,"top":0.6121309,"width":0.047041222,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Improve processing Webhooks jobs distribution","depth":18,"bounds":{"left":0.3700133,"top":0.63168395,"width":0.101894945,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Optimize ImportOpportunityBatch:","depth":18,"bounds":{"left":0.3700133,"top":0.65163606,"width":0.07396942,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cache OpportunitySyncableFields, OwnerProfiles, RecordType","depth":20,"bounds":{"left":0.37799203,"top":0.6683959,"width":0.13347739,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"remove findContactByConfigurationAndId - Contact ID already exist","depth":20,"bounds":{"left":0.37799203,"top":0.68834794,"width":0.1456117,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"add timings log","depth":20,"bounds":{"left":0.37799203,"top":0.70790106,"width":0.032912236,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.73623306,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"bounds":{"left":0.33776596,"top":0.7960894,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.79688746,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"bounds":{"left":0.5934175,"top":0.80606544,"width":0.012965426,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago","depth":13,"bounds":{"left":0.3620346,"top":0.79688746,"width":0.22240691,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.39744017,"top":0.80486834,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.42436835,"top":0.8032721,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.42436835,"top":0.80486834,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.3620346,"top":0.86951315,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"bounds":{"left":0.37200797,"top":0.86951315,"width":0.013796543,"height":0.0207502},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"bounds":{"left":0.37416887,"top":0.8747007,"width":0.004155585,"height":0.012769354},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.38098404,"top":0.8747007,"width":0.0018284575,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"bounds":{"left":0.33776596,"top":0.9293695,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.9301676,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 4 days ago •","depth":13,"bounds":{"left":0.3620346,"top":0.9301676,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"bounds":{"left":0.3804854,"top":0.9397446,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.390625,"top":0.93814844,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.41738698,"top":0.9365523,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.41738698,"top":0.93814844,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"bounds":{"left":0.44232047,"top":0.93814844,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"bounds":{"left":0.44581118,"top":0.9365523,"width":0.020113032,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"bounds":{"left":0.44581118,"top":0.93814844,"width":0.014793883,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"bounds":{"left":0.3620346,"top":0.97525936,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 59s","depth":18,"bounds":{"left":0.43650267,"top":0.97525936,"width":0.03756649,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"bounds":{"left":0.47406915,"top":0.97525936,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.3620346,"top":1.0,"width":0.25731382,"height":-0.0315243},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.03523936,"height":-0.031923413},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the approach is clean — extracting the shared logic into","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.13696809,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsCommandTrait","depth":18,"bounds":{"left":0.5006649,"top":1.0,"width":0.0546875,"height":-0.065043926},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a good call, and the scheduling design (offset cron at","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.24335106,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":01","depth":18,"bounds":{"left":0.43567154,"top":1.0,"width":0.0071476065,"height":-0.08180368},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to avoid spikes) makes sense. I found two bugs and a few minor points.","depth":17,"bounds":{"left":0.44448137,"top":1.0,"width":0.15425532,"height":-0.07980847},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Bugs","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bugs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Null dereference on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:49-51","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"$team->crm","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be null if the team has no CRM configuration record. Accessing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->provider","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on null will throw a fatal error. The existing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job handles this by using","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"??","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig->provider ?? 'unknown'","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). The new job needs a null guard here:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"===","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"||","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[SyncHubspotObjects] Team has no HubSpot CRM config","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]);","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$lastSyncedAt","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is nullable but","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requires","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:85,88,116","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"last_synced_at","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// can be null on first run","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// ...","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HubspotInterface","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"void","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"With","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", passing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-typed parameter throws a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TypeError","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". A team that has never synced before will crash on the first run. The parameter should be","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"?Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(and","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncOpportunities","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should handle null), or provide a fallback like","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon::createFromTimestamp(0)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Minor Issues","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor Issues","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Double call to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix()","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() ?","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() .","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"''","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"getLogPrefix()","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called twice. Trivial but unnecessary — capture the result in a variable first.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"command doesn't guard HubSpot when","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{team}","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"argument is given —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/SyncObjects.php:40-41","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teams","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[] = Team::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"idOrUuId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// no provider check","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Running","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-objects <hubspot-team-id>","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dispatches a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsJob","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Design Notes","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Design Notes","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Scheduler placement","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is placed inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"scheduleEveryFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but uses a manual cron","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead of","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->everyFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQS visibility timeout","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncHubspotObjects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6731732486630048521
|
8092255850742611275
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects...
|
60914
|
|
52557
|
1137
|
34
|
2026-04-20T07:22:01.925603+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776669721925_m2.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.09524601,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.15525267,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.06981383,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.10688165,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.12915559,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.014960106,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.06200133,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Events","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Events","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.030418882,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.2052859,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"bounds":{"left":0.083277926,"top":0.06943336,"width":0.026761968,"height":0.014764565},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"bounds":{"left":0.083277926,"top":0.06943336,"width":0.026761968,"height":0.014764565},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"bounds":{"left":0.1783577,"top":0.06424581,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"bounds":{"left":0.082446806,"top":0.09976058,"width":0.107546546,"height":0.025538707},"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.19547872,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.19547872,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.20079787,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.21542554,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.2287234,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.23071809,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.2526596,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.25465426,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.8171542,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.8294548,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.84258646,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.8465758,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88480717,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.89511305,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.91173536,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.9310173,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94431514,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.95761305,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.9709109,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.98420876,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.19514628,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.19514628,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.20079787,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.21160239,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"bounds":{"left":0.22855718,"top":0.09936153,"width":0.05501995,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.23919548,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.2711104,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"bounds":{"left":0.2741024,"top":0.113727055,"width":0.0056515955,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.27975398,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.2862367,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.29720744,"top":0.10574621,"width":0.01512633,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.3179854,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.32912233,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.3508976,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.36186835,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"bounds":{"left":0.37649602,"top":0.09936153,"width":0.070644945,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.3882979,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.4346742,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"bounds":{"left":0.43766624,"top":0.113727055,"width":0.0056515955,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.44331783,"top":0.113727055,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.44980052,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.46110374,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.4837101,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.4948471,"top":0.10574621,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.20910904,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.20910904,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.20910904,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.42503324,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.42503324,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.46559176,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.5482048,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.5482048,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.60039896,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.9865359,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"bounds":{"left":0.3957779,"top":0.1915403,"width":0.32081118,"height":0.06384677},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"bounds":{"left":0.3957779,"top":0.19233839,"width":0.27194148,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.3984375,"top":0.22426178,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"bounds":{"left":0.40492022,"top":0.22426178,"width":0.027759308,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.4340093,"top":0.22665602,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"bounds":{"left":0.71924865,"top":0.19832402,"width":0.05119681,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"bounds":{"left":0.7315492,"top":0.20430966,"width":0.034574468,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.77177525,"top":0.19832402,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.77609706,"top":0.20430966,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.40641624,"top":0.2677574,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"bounds":{"left":0.42503324,"top":0.26456505,"width":0.044215426,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"bounds":{"left":0.42503324,"top":0.2661612,"width":0.044215426,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"bounds":{"left":0.47057846,"top":0.2661612,"width":0.06333112,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.53523934,"top":0.264166,"width":0.018450798,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.53723407,"top":0.26735833,"width":0.014461436,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.55502,"top":0.2661612,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"bounds":{"left":0.56632316,"top":0.264166,"width":0.061668884,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"bounds":{"left":0.56831783,"top":0.26735833,"width":0.057679523,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.6293218,"top":0.26177174,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"bounds":{"left":0.76761967,"top":0.3180367,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"bounds":{"left":0.3957779,"top":0.30007982,"width":0.057347074,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.40940824,"top":0.30965683,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4474734,"top":0.30965683,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.4504654,"top":0.30965683,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.45329124,"top":0.30965683,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"bounds":{"left":0.453125,"top":0.30007982,"width":0.047706116,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.46675533,"top":0.30965683,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.49517953,"top":0.30965683,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"bounds":{"left":0.49817154,"top":0.30965683,"width":0.0019946808,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.50016624,"top":0.30965683,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (6)","depth":16,"bounds":{"left":0.5008311,"top":0.30007982,"width":0.04504654,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.51446146,"top":0.30965683,"width":0.015791224,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.54022604,"top":0.30965683,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":18,"bounds":{"left":0.5432181,"top":0.30965683,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.5462101,"top":0.30965683,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"bounds":{"left":0.54587764,"top":0.30007982,"width":0.058344416,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.55950797,"top":0.30965683,"width":0.029920213,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.59857047,"top":0.30965683,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"bounds":{"left":0.6015625,"top":0.30965683,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.6037234,"top":0.30965683,"width":0.0018284575,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.3957779,"top":0.34557062,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.3957779,"top":0.34836394,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@github-actions","depth":12,"bounds":{"left":0.3957779,"top":0.34557062,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.6693817,"top":0.3463687,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"github-actions bot commented 5 days ago •","depth":14,"bounds":{"left":0.42004654,"top":0.3463687,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions","depth":16,"bounds":{"left":0.42004654,"top":0.35434955,"width":0.03307846,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions","depth":17,"bounds":{"left":0.42004654,"top":0.35434955,"width":0.03307846,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":17,"bounds":{"left":0.4566157,"top":0.35594574,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.46675533,"top":0.35434955,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5 days ago","depth":15,"bounds":{"left":0.49351728,"top":0.3527534,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":17,"bounds":{"left":0.49351728,"top":0.35434955,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"bounds":{"left":0.5184508,"top":0.35434955,"width":0.0019946808,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"bounds":{"left":0.52177525,"top":0.3527534,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"bounds":{"left":0.52177525,"top":0.35434955,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Security dependency updates — composer — 2026-04-15","depth":16,"bounds":{"left":0.42004654,"top":0.38986433,"width":0.25731382,"height":0.026735835},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security dependency updates — composer — 2026-04-15","depth":17,"bounds":{"left":0.42004654,"top":0.39026338,"width":0.18633644,"height":0.019952115},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see","depth":17,"bounds":{"left":0.42004654,"top":0.4309657,"width":0.23869681,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":18,"bounds":{"left":0.47822472,"top":0.44772545,"width":0.026097074,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"below.","depth":17,"bounds":{"left":0.5043218,"top":0.44772545,"width":0.016289894,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CI run logs →","depth":17,"bounds":{"left":0.5206117,"top":0.44772545,"width":0.028590426,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CI run logs →","depth":18,"bounds":{"left":0.5206117,"top":0.44772545,"width":0.028590426,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Upgrade safety (changelog review)","depth":16,"bounds":{"left":0.42004654,"top":0.4820431,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Upgrade safety (changelog review)","depth":17,"bounds":{"left":0.42004654,"top":0.48244214,"width":0.09591091,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall verdict:","depth":18,"bounds":{"left":0.42004654,"top":0.5139665,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed","depth":18,"bounds":{"left":0.4554521,"top":0.5139665,"width":0.013464096,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert","depth":17,"bounds":{"left":0.46891624,"top":0.5139665,"width":0.16988032,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#463","depth":17,"bounds":{"left":0.63879657,"top":0.5139665,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#463","depth":18,"bounds":{"left":0.63879657,"top":0.5139665,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(phpunit/phpunit) is listed under","depth":17,"bounds":{"left":0.42004654,"top":0.5139665,"width":0.25116357,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Skipped alerts","depth":18,"bounds":{"left":0.4709109,"top":0.53072625,"width":0.032413565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.","depth":17,"bounds":{"left":0.42004654,"top":0.53072625,"width":0.2435173,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This does not replace CI, tests, or manual smoke checks before merge.","depth":18,"bounds":{"left":0.42004654,"top":0.547486,"width":0.2534907,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Fixed alerts","depth":16,"bounds":{"left":0.42004654,"top":0.59856343,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":17,"bounds":{"left":0.42004654,"top":0.5989625,"width":0.031416222,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"bounds":{"left":0.42486703,"top":0.6444533,"width":0.010970744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"bounds":{"left":0.4509641,"top":0.6444533,"width":0.018949468,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"bounds":{"left":0.48470744,"top":0.6444533,"width":0.018450798,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"bounds":{"left":0.5270944,"top":0.6444533,"width":0.009474734,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"bounds":{"left":0.5611702,"top":0.6360734,"width":0.018450798,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"bounds":{"left":0.5894282,"top":0.6444533,"width":0.024102394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"bounds":{"left":0.6409575,"top":0.6444533,"width":0.013297873,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"bounds":{"left":0.4247008,"top":0.7134876,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"bounds":{"left":0.4247008,"top":0.7134876,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"bounds":{"left":0.44514626,"top":0.70510775,"width":0.018949468,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.7134876,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"bounds":{"left":0.5121343,"top":0.7134876,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"bounds":{"left":0.5121343,"top":0.7134876,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"bounds":{"left":0.56050533,"top":0.7134876,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.7134876,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.7134876,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.67996806,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"bounds":{"left":0.6225067,"top":0.6967279,"width":0.048537236,"height":0.04708699},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":0.7490024,"width":0.035738032,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":0.7470072,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"bounds":{"left":0.4247008,"top":0.8160415,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"bounds":{"left":0.4247008,"top":0.8160415,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"bounds":{"left":0.44514626,"top":0.8076616,"width":0.01861702,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.8160415,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"bounds":{"left":0.5121343,"top":0.8160415,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"bounds":{"left":0.5121343,"top":0.8160415,"width":0.036402926,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"bounds":{"left":0.56050533,"top":0.8160415,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.8160415,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.8160415,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.7741421,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"bounds":{"left":0.6225067,"top":0.79090184,"width":0.048537236,"height":0.06384677},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":0.8599362,"width":0.035738032,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":0.8579409,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"bounds":{"left":0.4247008,"top":0.9185954,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"bounds":{"left":0.4247008,"top":0.9185954,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.44514626,"top":0.9102155,"width":0.022273935,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"bounds":{"left":0.48470744,"top":0.9185954,"width":0.00930851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"bounds":{"left":0.5121343,"top":0.9185954,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"bounds":{"left":0.5121343,"top":0.9185954,"width":0.03939495,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"bounds":{"left":0.56050533,"top":0.9185954,"width":0.01412899,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":0.9185954,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":0.9185954,"width":0.017785905,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.8850758,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"bounds":{"left":0.6225067,"top":0.9018356,"width":0.048537236,"height":0.04708699},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"bounds":{"left":0.6225067,"top":0.95211494,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"bounds":{"left":0.63248,"top":0.95211494,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.4247008,"top":1.0,"width":0.011469414,"height":-0.029529095},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.4247008,"top":1.0,"width":0.011469414,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"bounds":{"left":0.44514626,"top":1.0,"width":0.02925532,"height":-0.021149278},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"bounds":{"left":0.48470744,"top":1.0,"width":0.017287234,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"bounds":{"left":0.5121343,"top":1.0,"width":0.03939495,"height":-0.029529095},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"bounds":{"left":0.5121343,"top":1.0,"width":0.03939495,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"bounds":{"left":0.56050533,"top":1.0,"width":0.011303191,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.5894282,"top":1.0,"width":0.017785905,"height":-0.029529095},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.5894282,"top":1.0,"width":0.017785905,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.6225067,"top":0.9792498,"width":0.049534574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via","depth":20,"bounds":{"left":0.6225067,"top":0.9960096,"width":0.048537236,"height":0.0039904118},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.6241689,"top":1.0,"width":0.035738032,"height":-0.08180368},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.66140294,"top":1.0,"width":0.0013297872,"height":-0.07980847},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Covered by bump to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#425).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"bounds":{"left":0.42486703,"top":0.6444533,"width":0.010970744,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"bounds":{"left":0.4247008,"top":0.7134876,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"bounds":{"left":0.4247008,"top":0.7134876,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"bounds":{"left":0.4247008,"top":0.8160415,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"bounds":{"left":0.4247008,"top":0.8160415,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"bounds":{"left":0.4247008,"top":0.9185954,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"bounds":{"left":0.4247008,"top":0.9185954,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.4247008,"top":1.0,"width":0.011469414,"height":-0.029529095},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.4247008,"top":1.0,"width":0.011469414,"height":-0.029529095},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
3908985108263003132
|
8091944412747804083
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 1 commit into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (1)
Commits
(
1
)
Checks (6)
Checks
(
6
)
Files changed (1)
Files changed
(
1
)
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454...
|
52556
|
|
52953
|
1148
|
17
|
2026-04-20T07:50:21.119396+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776671421119_m1.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (2)
Commits
(
2
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 2 commits into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Events","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Events","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (28)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ready to merge","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ready to merge","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 2 commits into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (2)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 2 commits into master from secfix/composer-20260415 Copy head branch name to clipboard","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"fix(security): composer dependency updates – 2026-04-15","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions[bot]","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions[bot]","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 2 commits into","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":19,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@github-actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"github-actions bot commented 5 days ago •","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5 days ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Security dependency updates — composer — 2026-04-15","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security dependency updates — composer — 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"below.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CI run logs →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CI run logs →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Upgrade safety (changelog review)","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Upgrade safety (changelog review)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall verdict:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#463","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#463","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(phpunit/phpunit) is listed under","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Skipped alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This does not replace CI, tests, or manual smoke checks before merge.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Fixed alerts","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Covered by bump to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#425).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4851116855680649293
|
8091935616923479472
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Events
Userpilot | Events
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (28)
Security and quality
(
28
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Ready to merge
Ready to merge
Code
Code
Open
github-actions[bot]
github-actions[bot]
wants to merge 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (2)
Commits
(
2
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Open
fix(security): composer dependency updates – 2026-04-15 #11970 github-actions[bot] wants to merge 2 commits into master from secfix/composer-20260415 Copy head branch name to clipboard
fix(security): composer dependency updates – 2026-04-15
fix(security): composer dependency updates – 2026-04-15
#
11970
github-actions[bot]
github-actions[bot]
wants to merge 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases...
|
NULL
|
|
53153
|
1150
|
37
|
2026-04-20T07:58:50.946132+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776671930946_m1.jpg...
|
Firefox
|
fix(security): composer dependency updates – 2026- fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11970
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Logged-activity
Userpilot | Logged-activity
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (22)
Security and quality
(
22
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
8 minutes ago
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (2)
Commits
(
2
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases
releases
releases
releases
releases
releases
releases
Notes
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454)....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20543] AJ Reports > Tracking - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Product Growth Platform | Userpilot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Product Growth Platform | Userpilot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Logged-activity","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Logged-activity","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (22)","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"fix(security): composer dependency updates – 2026-04-15 #11970 Edit title","depth":13,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fix(security): composer dependency updates – 2026-04-15","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11970","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Code","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Merged","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"merged 2 commits into","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"secfix/composer-20260415","depth":16,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"secfix/composer-20260415","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"8 minutes ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lines changed: 23 additions & 23 deletions","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (2)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (5)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@github-actions","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"github-actions bot commented 5 days ago •","depth":14,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"github-actions","depth":16,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"github-actions","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5 days ago","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5 days ago","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Security dependency updates — composer — 2026-04-15","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security dependency updates — composer — 2026-04-15","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"below.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CI run logs →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CI run logs →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Upgrade safety (changelog review)","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Upgrade safety (changelog review)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall verdict:","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#463","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#463","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(phpunit/phpunit) is listed under","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Skipped alerts","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This does not replace CI, tests, or manual smoke checks before merge.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Fixed alerts","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fixed alerts","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.32256943,"top":0.0,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.32256943,"top":0.0,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"bounds":{"left":0.36527777,"top":0.0,"width":0.06111111,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"bounds":{"left":0.44791666,"top":0.0,"width":0.036111113,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"bounds":{"left":0.5052083,"top":0.0,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"bounds":{"left":0.5052083,"top":0.0,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"bounds":{"left":0.60625,"top":0.0,"width":0.023611112,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.6666667,"top":0.0,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.6666667,"top":0.0,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via","depth":20,"bounds":{"left":0.7357639,"top":0.0,"width":0.10138889,"height":0.112222224},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"bounds":{"left":0.7392361,"top":0.03888889,"width":0.074652776,"height":0.016111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"bounds":{"left":0.81701386,"top":0.036111113,"width":0.0027777778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"bounds":{"left":0.32256943,"top":0.12055556,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"bounds":{"left":0.32256943,"top":0.12055556,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.36527777,"top":0.10888889,"width":0.046527777,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"bounds":{"left":0.44791666,"top":0.12055556,"width":0.015277778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"bounds":{"left":0.5052083,"top":0.12055556,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"bounds":{"left":0.5052083,"top":0.12055556,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"bounds":{"left":0.60625,"top":0.12055556,"width":0.029513888,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.6666667,"top":0.12055556,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.6666667,"top":0.12055556,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"bounds":{"left":0.7357639,"top":0.07388889,"width":0.103472225,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Covered by bump to 3.0.51 (also","depth":20,"bounds":{"left":0.7357639,"top":0.097222224,"width":0.10138889,"height":0.06555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"bounds":{"left":0.7357639,"top":0.16722222,"width":0.020833334,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#425).","depth":20,"bounds":{"left":0.7565972,"top":0.16722222,"width":0.033333335,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Alert","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#457","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#457","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#434","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#434","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#425","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#425","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#429","depth":20,"bounds":{"left":0.32256943,"top":0.0,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#429","depth":21,"bounds":{"left":0.32256943,"top":0.0,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"#454","depth":20,"bounds":{"left":0.32256943,"top":0.12055556,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"#454","depth":21,"bounds":{"left":0.32256943,"top":0.12055556,"width":0.023958333,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Package","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel/passport","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"google/protobuf","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"league/commonmark","depth":20,"bounds":{"left":0.36527777,"top":0.0,"width":0.06111111,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"phpseclib/phpseclib","depth":20,"bounds":{"left":0.36527777,"top":0.10888889,"width":0.046527777,"height":0.04222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Severity","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"high","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"medium","depth":20,"bounds":{"left":0.44791666,"top":0.0,"width":0.036111113,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"low","depth":20,"bounds":{"left":0.44791666,"top":0.12055556,"width":0.015277778,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CVE","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-39976","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-39976","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-6409","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-6409","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-32935","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-32935","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-33347","depth":20,"bounds":{"left":0.5052083,"top":0.0,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-33347","depth":21,"bounds":{"left":0.5052083,"top":0.0,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CVE-2026-40194","depth":20,"bounds":{"left":0.5052083,"top":0.12055556,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CVE-2026-40194","depth":21,"bounds":{"left":0.5052083,"top":0.12055556,"width":0.08229167,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patched version","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13.7.1","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.33.6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.50","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.8.2","depth":20,"bounds":{"left":0.60625,"top":0.0,"width":0.023611112,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3.0.51","depth":20,"bounds":{"left":0.60625,"top":0.12055556,"width":0.029513888,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changelog","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.6666667,"top":0.0,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.6666667,"top":0.0,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"releases","depth":20,"bounds":{"left":0.6666667,"top":0.12055556,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"releases","depth":21,"bounds":{"left":0.6666667,"top":0.12055556,"width":0.03715278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notes","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"composer update","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Breaking-change risk:","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"fixes","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#454).","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
6444701175264491650
|
8091842639473290416
|
click
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
JY-20692 change confirmation parameter by LakyLak · Pull Request #11986 · jiminny/app
[JY-20543] AJ Reports > Tracking - Jira
[JY-20543] AJ Reports > Tracking - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
[JY-18909] [Part2] Automated reports with Ask Jiminny - Jira
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
Ask Jiminny Reports by nikolay-yankov · Pull Request #11894 · jiminny/app
New Tab
New Tab
Product Growth Platform | Userpilot
Product Growth Platform | Userpilot
Userpilot | Logged-activity
Userpilot | Logged-activity
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
fix(security): composer dependency updates – 2026-04-15 by github-actions[bot] · Pull Request #11970 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (22)
Security and quality
(
22
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
fix(security): composer dependency updates – 2026-04-15 #11970 Edit title
fix(security): composer dependency updates – 2026-04-15
#
11970
Edit title
Code
Code
Merged
LakyLak
LakyLak
merged 2 commits into
master
master
from
secfix/composer-20260415
secfix/composer-20260415
Copy head branch name to clipboard
8 minutes ago
Lines changed: 23 additions & 23 deletions
Conversation (3)
Conversation
(
3
)
Commits (2)
Commits
(
2
)
Checks (5)
Checks
(
5
)
Files changed (1)
Files changed
(
1
)
Conversation
Conversation
@github-actions
Show options
github-actions bot commented 5 days ago •
github-actions
github-actions
bot
commented
5 days ago
5 days ago
•
edited
edited
Security dependency updates — composer — 2026-04-15
Security dependency updates — composer — 2026-04-15
This PR was opened automatically by the secfix bot. For this ecosystem, one commit carries every dependency upgrade from this run; see
Fixed alerts
below.
CI run logs →
CI run logs →
Upgrade safety (changelog review)
Upgrade safety (changelog review)
Overall verdict:
Mixed
— All previously-actionable alerts were fixed as safe patch/minor bumps. Alert
#463
#463
(phpunit/phpunit) is listed under
Skipped alerts
: the patched version (12.5.22) requires a major version jump from 11.x that includes documented breaking API removals and requires manual migration.
This does not replace CI, tests, or manual smoke checks before merge.
Fixed alerts
Fixed alerts
Alert
Package
Severity
CVE
Patched version
Changelog
Notes
#457
#457
laravel/passport
high
CVE-2026-39976
CVE-2026-39976
13.7.1
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
#434
#434
google/protobuf
high
CVE-2026-6409
CVE-2026-6409
4.33.6
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
#425
#425
phpseclib/phpseclib
high
CVE-2026-32935
CVE-2026-32935
3.0.50
releases
releases
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454).
#429
#429
league/commonmark
medium
CVE-2026-33347
CVE-2026-33347
2.8.2
releases
releases
Breaking-change risk:
none observed (patch/minor). Transitive dep (via laravel/framework); bumped from 2.8.1 to 2.8.2 via
composer update
.
#454
#454
phpseclib/phpseclib
low
CVE-2026-40194
CVE-2026-40194
3.0.51
releases
releases
Breaking-change risk:
none observed (patch/minor). Covered by bump to 3.0.51 (also
fixes
#425).
Alert
#457
#457
#434
#434
#425
#425
#429
#429
#454
#454
Package
laravel/passport
google/protobuf
phpseclib/phpseclib
league/commonmark
phpseclib/phpseclib
Severity
high
high
high
medium
low
CVE
CVE-2026-39976
CVE-2026-39976
CVE-2026-6409
CVE-2026-6409
CVE-2026-32935
CVE-2026-32935
CVE-2026-33347
CVE-2026-33347
CVE-2026-40194
CVE-2026-40194
Patched version
13.7.1
4.33.6
3.0.50
2.8.2
3.0.51
Changelog
releases
releases
releases
releases
releases
releases
releases
releases
releases
releases
Notes
Breaking-change risk:
none observed (patch/minor). Bumped from v13.6.0 to v13.7.4 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Transitive dep; bumped from v4.33.5 to v4.33.6 via
composer update
.
Breaking-change risk:
none observed (patch/minor). Bumped from 3.0.49 to 3.0.51 (also
fixes
#454)....
|
53151
|
|
60923
|
1313
|
46
|
2026-04-21T06:25:40.654136+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776752740654_m2.jpg...
|
Firefox
|
JY-20701 | Reschedule HubSpot Sync Objects by yalo JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11989
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects
uses 1 hour (
60 * 60
) vs
SyncObjects
's 6 hours (
60 * 60 * 6
). Appropriate given the more frequent, presumably lighter runs.
Stagger delay
: HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.
Add or remove reactions
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20553-debug-crm-sync-delays
branch from
f6c0ec3
f6c0ec3
to
5928f60
5928f60
Compare
Compare
yesterday
yesterday
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
3d7234a
3d7234a
to
1d10796
1d10796
Compare
Compare
yesterday
yesterday
Base automatically changed from
JY-20553-debug-crm-sync-delays
to
master
19 hours ago
19 hours ago
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
fec25f3
fec25f3
to
f0119c9
f0119c9
Compare
Compare
19 hours ago
19 hours ago
yalokin-jiminny
yalokin-jiminny
added
17
commits
15 hours ago
15 hours ago
@yalokin-jiminny
JY-20701
JY-20701
| remove HubSpot from 30 min scheduler
| remove HubSpot from 30 min scheduler
2f38f13
2f38f13
@yalokin-jiminny
JY-20701
JY-20701
| Add SyncHubspotObjects Job
| Add SyncHubspotObjects Job
50b0e20
50b0e20
@yalokin-jiminny
JY-20701
JY-20701
| Add HubSpot sync objects scheduler each 5 min
| Add HubSpot sync objects scheduler each 5 min
ea92c0f
ea92c0f
@yalokin-jiminny
JY-20701
JY-20701
| extract duplicated code
| extract duplicated code
7ecdad3
7ecdad3
@yalokin-jiminny
JY-20701
JY-20701
| cs
| cs
525cbb2
525cbb2
@yalokin-jiminny
JY-20701
JY-20701
| safe crm check
| safe crm check
35de0d5
35de0d5
@yalokin-jiminny
JY-20701
JY-20701
| null on first-time sync
| null on first-time sync
ac1f89e
ac1f89e
@yalokin-jiminny
JY-20701
JY-20701
| minor improvement
| minor improvement
bab377e
bab377e
@yalokin-jiminny
JY-20701
JY-20701
| improve filter by team
| improve filter by team
f39dc56
f39dc56
@yalokin-jiminny
JY-20701
JY-20701
| add scheduler comment
| add scheduler comment
f2109d7
f2109d7
@yalokin-jiminny
JY-20701
JY-20701
| improve delays
| improve delays
f929670
f929670
@yalokin-jiminny
JY-20701
JY-20701
| add tests
| add tests
9691864
9691864
@yalokin-jiminny
JY-20701
JY-20701
| improve webhook delays
| improve webhook delays
a3a308f
a3a308f
@yalokin-jiminny
JY-20701
JY-20701
| update tests
| update tests
64251d2
64251d2
@yalokin-jiminny
JY-20701
JY-20701
| add OpportunitySyncTrait timings log
| add OpportunitySyncTrait timings log
42e30cf
42e30cf
@yalokin-jiminny
JY-20701
JY-20701
| add cache queries to optimize performance
| add cache queries to optimize performance
417c62d
417c62d
@yalokin-jiminny...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny MCP Connector - Product - Confluence","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny MCP Connector - Product - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.08294548,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.15791224,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.02144282,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.08610372,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.042719416,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.013131649,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.1740359,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.054521278,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.004986702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.16323139,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.16988032,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18085106,"top":0.10574621,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20162898,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21276596,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23454122,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24534574,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (21)","depth":12,"bounds":{"left":0.2601396,"top":0.09936153,"width":0.069980055,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27194148,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31831783,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21","depth":14,"bounds":{"left":0.32130983,"top":0.113727055,"width":0.0048204786,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.32613033,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33277926,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34391624,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.36668882,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.3778258,"top":0.10574621,"width":0.01761968,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.09325133,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.34973404,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.48454124,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.98636967,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Review requested","depth":15,"bounds":{"left":0.35006648,"top":0.2019154,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Review requested","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.039893616,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested your review on this pull request.","depth":15,"bounds":{"left":0.3854721,"top":0.20351157,"width":0.09158909,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add your review","depth":14,"bounds":{"left":0.70212764,"top":0.19872306,"width":0.036901597,"height":0.022346368},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add your review","depth":16,"bounds":{"left":0.70511967,"top":0.20391062,"width":0.030917553,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.24221867,"width":0.26230052,"height":0.031923383},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects","depth":14,"bounds":{"left":0.33776596,"top":0.24301676,"width":0.2122673,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.55269283,"top":0.24301676,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11989","depth":15,"bounds":{"left":0.55917555,"top":0.24301676,"width":0.028922873,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.5894282,"top":0.24541101,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"bounds":{"left":0.6569149,"top":0.24860336,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"bounds":{"left":0.66921544,"top":0.254589,"width":0.038896278,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.24860336,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.254589,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.34840426,"top":0.28651237,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.36702126,"top":0.28332004,"width":0.034075797,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.36702126,"top":0.2849162,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 22 commits into","depth":15,"bounds":{"left":0.40242687,"top":0.2849162,"width":0.069148935,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.47290558,"top":0.282921,"width":0.018284574,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.47490028,"top":0.28611332,"width":0.014295213,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.49251994,"top":0.2849162,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701-reschedule-HubSpot-processing","depth":16,"bounds":{"left":0.50382316,"top":0.282921,"width":0.09524601,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":17,"bounds":{"left":0.50581783,"top":0.28611332,"width":0.09125665,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.60039896,"top":0.28052673,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 949 additions & 97 deletions","depth":14,"bounds":{"left":0.7067819,"top":0.3367917,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (5)","depth":16,"bounds":{"left":0.33776596,"top":0.31883478,"width":0.057347074,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35139626,"top":0.32841182,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38946143,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"bounds":{"left":0.39245346,"top":0.32841182,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.39527926,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (22)","depth":16,"bounds":{"left":0.39511302,"top":0.31883478,"width":0.05069814,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.40874335,"top":0.32841182,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4401596,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":18,"bounds":{"left":0.4431516,"top":0.32841182,"width":0.005485372,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.44863698,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"bounds":{"left":0.44581118,"top":0.31883478,"width":0.04504654,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.45944148,"top":0.32841182,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.48520613,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.48819813,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.49119017,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (11)","depth":16,"bounds":{"left":0.49085772,"top":0.31883478,"width":0.060339097,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.50448805,"top":0.32841182,"width":0.029753989,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.5455452,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11","depth":18,"bounds":{"left":0.54853725,"top":0.32841182,"width":0.0043218085,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.55285907,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.36711892,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.3651237,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago •","depth":14,"bounds":{"left":0.3620346,"top":0.3651237,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":17,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.39744017,"top":0.37310454,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":15,"bounds":{"left":0.42436835,"top":0.3715084,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":17,"bounds":{"left":0.42436835,"top":0.37310454,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"bounds":{"left":0.44930187,"top":0.37310454,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"bounds":{"left":0.45262632,"top":0.3715084,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"bounds":{"left":0.45262632,"top":0.37310454,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20701","depth":16,"bounds":{"left":0.3620346,"top":0.40822026,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"bounds":{"left":0.3620346,"top":0.4086193,"width":0.015791224,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701","depth":17,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":18,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Casus:","depth":16,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Casus:","depth":17,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.015292553,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"We want more fluent curve of the jobs distribution (no spikes) in","depth":18,"bounds":{"left":0.3700133,"top":0.47326416,"width":0.13879654,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm-sync","depth":19,"bounds":{"left":0.51047206,"top":0.47525936,"width":0.018949468,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"queue","depth":18,"bounds":{"left":0.53108376,"top":0.47326416,"width":0.01462766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times","depth":18,"bounds":{"left":0.3700133,"top":0.49281725,"width":0.24750665,"height":0.030726258},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.021110373,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Remove HubSpot from SyncObjects","depth":18,"bounds":{"left":0.3700133,"top":0.5726257,"width":0.07712766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Create separate HubSpotSyncObjects that runs each 5 min instead of each 30","depth":18,"bounds":{"left":0.3700133,"top":0.59217876,"width":0.16855054,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Extract common code","depth":18,"bounds":{"left":0.3700133,"top":0.6121309,"width":0.047041222,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Improve processing Webhooks jobs distribution","depth":18,"bounds":{"left":0.3700133,"top":0.63168395,"width":0.101894945,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Optimize ImportOpportunityBatch:","depth":18,"bounds":{"left":0.3700133,"top":0.65163606,"width":0.07396942,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cache OpportunitySyncableFields, OwnerProfiles, RecordType","depth":20,"bounds":{"left":0.37799203,"top":0.6683959,"width":0.13347739,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"remove findContactByConfigurationAndId - Contact ID already exist","depth":20,"bounds":{"left":0.37799203,"top":0.68834794,"width":0.1456117,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"add timings log","depth":20,"bounds":{"left":0.37799203,"top":0.70790106,"width":0.032912236,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.73623306,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"bounds":{"left":0.33776596,"top":0.7960894,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.79688746,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"bounds":{"left":0.5934175,"top":0.80606544,"width":0.012965426,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago","depth":13,"bounds":{"left":0.3620346,"top":0.79688746,"width":0.22240691,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.39744017,"top":0.80486834,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.42436835,"top":0.8032721,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.42436835,"top":0.80486834,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.3620346,"top":0.86951315,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"bounds":{"left":0.37200797,"top":0.86951315,"width":0.013796543,"height":0.0207502},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"bounds":{"left":0.37416887,"top":0.8747007,"width":0.004155585,"height":0.012769354},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.38098404,"top":0.8747007,"width":0.0018284575,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"bounds":{"left":0.33776596,"top":0.9293695,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.9301676,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 4 days ago •","depth":13,"bounds":{"left":0.3620346,"top":0.9301676,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"bounds":{"left":0.3804854,"top":0.9397446,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.390625,"top":0.93814844,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.41738698,"top":0.9365523,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.41738698,"top":0.93814844,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"bounds":{"left":0.44232047,"top":0.93814844,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"bounds":{"left":0.44581118,"top":0.9365523,"width":0.020113032,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"bounds":{"left":0.44581118,"top":0.93814844,"width":0.014793883,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"bounds":{"left":0.3620346,"top":0.97525936,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 59s","depth":18,"bounds":{"left":0.43650267,"top":0.97525936,"width":0.03756649,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"bounds":{"left":0.47406915,"top":0.97525936,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.3620346,"top":1.0,"width":0.25731382,"height":-0.0315243},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.03523936,"height":-0.031923413},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the approach is clean — extracting the shared logic into","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.13696809,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsCommandTrait","depth":18,"bounds":{"left":0.5006649,"top":1.0,"width":0.0546875,"height":-0.065043926},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a good call, and the scheduling design (offset cron at","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.24335106,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":01","depth":18,"bounds":{"left":0.43567154,"top":1.0,"width":0.0071476065,"height":-0.08180368},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to avoid spikes) makes sense. I found two bugs and a few minor points.","depth":17,"bounds":{"left":0.44448137,"top":1.0,"width":0.15425532,"height":-0.07980847},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Bugs","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bugs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Null dereference on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:49-51","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"$team->crm","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be null if the team has no CRM configuration record. Accessing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->provider","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on null will throw a fatal error. The existing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job handles this by using","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"??","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig->provider ?? 'unknown'","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). The new job needs a null guard here:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"===","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"||","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[SyncHubspotObjects] Team has no HubSpot CRM config","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]);","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$lastSyncedAt","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is nullable but","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requires","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:85,88,116","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"last_synced_at","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// can be null on first run","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// ...","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HubspotInterface","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"void","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"With","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", passing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-typed parameter throws a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TypeError","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". A team that has never synced before will crash on the first run. The parameter should be","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"?Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(and","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncOpportunities","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should handle null), or provide a fallback like","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon::createFromTimestamp(0)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Minor Issues","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor Issues","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Double call to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix()","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() ?","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() .","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"''","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"getLogPrefix()","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called twice. Trivial but unnecessary — capture the result in a variable first.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"command doesn't guard HubSpot when","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{team}","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"argument is given —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/SyncObjects.php:40-41","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teams","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[] = Team::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"idOrUuId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// no provider check","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Running","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-objects <hubspot-team-id>","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dispatches a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsJob","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Design Notes","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Design Notes","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Scheduler placement","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is placed inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"scheduleEveryFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but uses a manual cron","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead of","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->everyFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQS visibility timeout","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncHubspotObjects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"uses 1 hour (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"60 * 60","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") vs","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s 6 hours (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"60 * 60 * 6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). Appropriate given the more frequent, presumably lighter runs.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stagger delay","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20553-debug-crm-sync-delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f6c0ec3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f6c0ec3","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5928f60","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5928f60","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yesterday","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yesterday","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"3d7234a","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3d7234a","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"1d10796","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1d10796","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yesterday","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yesterday","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Base automatically changed from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20553-debug-crm-sync-delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"master","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"19 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"19 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fec25f3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fec25f3","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f0119c9","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f0119c9","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"19 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"19 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"15 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| remove HubSpot from 30 min scheduler","depth":14,"help_text":"JY-20701 | remove HubSpot from 30 min scheduler","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| remove HubSpot from 30 min scheduler","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2f38f13","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2f38f13","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| Add SyncHubspotObjects Job","depth":14,"help_text":"JY-20701 | Add SyncHubspotObjects Job","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| Add SyncHubspotObjects Job","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"50b0e20","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"50b0e20","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| Add HubSpot sync objects scheduler each 5 min","depth":14,"help_text":"JY-20701 | Add HubSpot sync objects scheduler each 5 min","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| Add HubSpot sync objects scheduler each 5 min","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"ea92c0f","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ea92c0f","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| extract duplicated code","depth":14,"help_text":"JY-20701 | extract duplicated code","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| extract duplicated code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"7ecdad3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"7ecdad3","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| cs","depth":14,"help_text":"JY-20701 | cs","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| cs","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"525cbb2","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"525cbb2","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| safe crm check","depth":14,"help_text":"JY-20701 | safe crm check","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| safe crm check","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"35de0d5","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"35de0d5","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| null on first-time sync","depth":14,"help_text":"JY-20701 | null on first-time sync","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| null on first-time sync","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"ac1f89e","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ac1f89e","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| minor improvement","depth":14,"help_text":"JY-20701 | minor improvement","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| minor improvement","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"bab377e","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"bab377e","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve filter by team","depth":14,"help_text":"JY-20701 | improve filter by team","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve filter by team","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f39dc56","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f39dc56","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add scheduler comment","depth":14,"help_text":"JY-20701 | add scheduler comment","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add scheduler comment","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f2109d7","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f2109d7","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve delays","depth":14,"help_text":"JY-20701 | improve delays","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f929670","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f929670","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add tests","depth":14,"help_text":"JY-20701 | add tests","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add tests","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"9691864","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"9691864","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve webhook delays","depth":14,"help_text":"JY-20701 | improve webhook delays","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve webhook delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"a3a308f","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"a3a308f","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| update tests","depth":14,"help_text":"JY-20701 | update tests","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| update tests","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"64251d2","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"64251d2","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add OpportunitySyncTrait timings log","depth":14,"help_text":"JY-20701 | add OpportunitySyncTrait timings log","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add OpportunitySyncTrait timings log","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"42e30cf","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"42e30cf","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add cache queries to optimize performance","depth":14,"help_text":"JY-20701 | add cache queries to optimize performance","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add cache queries to optimize performance","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"417c62d","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"417c62d","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
9059643401122972191
|
8090106237312744777
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects
uses 1 hour (
60 * 60
) vs
SyncObjects
's 6 hours (
60 * 60 * 6
). Appropriate given the more frequent, presumably lighter runs.
Stagger delay
: HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.
Add or remove reactions
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20553-debug-crm-sync-delays
branch from
f6c0ec3
f6c0ec3
to
5928f60
5928f60
Compare
Compare
yesterday
yesterday
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
3d7234a
3d7234a
to
1d10796
1d10796
Compare
Compare
yesterday
yesterday
Base automatically changed from
JY-20553-debug-crm-sync-delays
to
master
19 hours ago
19 hours ago
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
fec25f3
fec25f3
to
f0119c9
f0119c9
Compare
Compare
19 hours ago
19 hours ago
yalokin-jiminny
yalokin-jiminny
added
17
commits
15 hours ago
15 hours ago
@yalokin-jiminny
JY-20701
JY-20701
| remove HubSpot from 30 min scheduler
| remove HubSpot from 30 min scheduler
2f38f13
2f38f13
@yalokin-jiminny
JY-20701
JY-20701
| Add SyncHubspotObjects Job
| Add SyncHubspotObjects Job
50b0e20
50b0e20
@yalokin-jiminny
JY-20701
JY-20701
| Add HubSpot sync objects scheduler each 5 min
| Add HubSpot sync objects scheduler each 5 min
ea92c0f
ea92c0f
@yalokin-jiminny
JY-20701
JY-20701
| extract duplicated code
| extract duplicated code
7ecdad3
7ecdad3
@yalokin-jiminny
JY-20701
JY-20701
| cs
| cs
525cbb2
525cbb2
@yalokin-jiminny
JY-20701
JY-20701
| safe crm check
| safe crm check
35de0d5
35de0d5
@yalokin-jiminny
JY-20701
JY-20701
| null on first-time sync
| null on first-time sync
ac1f89e
ac1f89e
@yalokin-jiminny
JY-20701
JY-20701
| minor improvement
| minor improvement
bab377e
bab377e
@yalokin-jiminny
JY-20701
JY-20701
| improve filter by team
| improve filter by team
f39dc56
f39dc56
@yalokin-jiminny
JY-20701
JY-20701
| add scheduler comment
| add scheduler comment
f2109d7
f2109d7
@yalokin-jiminny
JY-20701
JY-20701
| improve delays
| improve delays
f929670
f929670
@yalokin-jiminny
JY-20701
JY-20701
| add tests
| add tests
9691864
9691864
@yalokin-jiminny
JY-20701
JY-20701
| improve webhook delays
| improve webhook delays
a3a308f
a3a308f
@yalokin-jiminny
JY-20701
JY-20701
| update tests
| update tests
64251d2
64251d2
@yalokin-jiminny
JY-20701
JY-20701
| add OpportunitySyncTrait timings log
| add OpportunitySyncTrait timings log
42e30cf
42e30cf
@yalokin-jiminny
JY-20701
JY-20701
| add cache queries to optimize performance
| add cache queries to optimize performance
417c62d
417c62d
@yalokin-jiminny...
|
NULL
|
|
60912
|
1313
|
39
|
2026-04-21T06:24:58.240791+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776752698240_m2.jpg...
|
Firefox
|
JY-20701 | Reschedule HubSpot Sync Objects by yalo JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/11989
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects
uses 1 hour (
60 * 60
) vs
SyncObjects
's 6 hours (
60 * 60 * 6
). Appropriate given the more frequent, presumably lighter runs.
Stagger delay
: HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.
Add or remove reactions
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20553-debug-crm-sync-delays
branch from
f6c0ec3
f6c0ec3
to
5928f60
5928f60
Compare
Compare
yesterday
yesterday
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
3d7234a
3d7234a
to
1d10796
1d10796
Compare
Compare
yesterday
yesterday
Base automatically changed from
JY-20553-debug-crm-sync-delays
to
master
19 hours ago
19 hours ago
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
fec25f3
fec25f3
to
f0119c9
f0119c9
Compare
Compare
19 hours ago
19 hours ago
yalokin-jiminny
yalokin-jiminny
added
17
commits
15 hours ago
15 hours ago
@yalokin-jiminny
JY-20701
JY-20701
| remove HubSpot from 30 min scheduler
| remove HubSpot from 30 min scheduler
2f38f13
2f38f13
@yalokin-jiminny
JY-20701
JY-20701
| Add SyncHubspotObjects Job
| Add SyncHubspotObjects Job
50b0e20
50b0e20
@yalokin-jiminny
JY-20701
JY-20701
| Add HubSpot sync objects scheduler each 5 min
| Add HubSpot sync objects scheduler each 5 min
ea92c0f
ea92c0f
@yalokin-jiminny
JY-20701
JY-20701
| extract duplicated code
| extract duplicated code
7ecdad3
7ecdad3
@yalokin-jiminny
JY-20701
JY-20701
| cs
| cs
525cbb2
525cbb2
@yalokin-jiminny
JY-20701
JY-20701
| safe crm check
| safe crm check
35de0d5
35de0d5
@yalokin-jiminny
JY-20701
JY-20701
| null on first-time sync
| null on first-time sync
ac1f89e
ac1f89e
@yalokin-jiminny
JY-20701
JY-20701
| minor improvement
| minor improvement
bab377e
bab377e
@yalokin-jiminny
JY-20701
JY-20701
| improve filter by team
| improve filter by team
f39dc56
f39dc56
@yalokin-jiminny
JY-20701
JY-20701
| add scheduler comment
| add scheduler comment
f2109d7
f2109d7
@yalokin-jiminny
JY-20701
JY-20701
| improve delays
| improve delays
f929670
f929670
@yalokin-jiminny
JY-20701
JY-20701
| add tests
| add tests
9691864
9691864
@yalokin-jiminny
JY-20701
JY-20701
| improve webhook delays
| improve webhook delays
a3a308f
a3a308f
@yalokin-jiminny
JY-20701
JY-20701
| update tests
| update tests
64251d2
64251d2
@yalokin-jiminny
JY-20701
JY-20701
| add OpportunitySyncTrait timings log
| add OpportunitySyncTrait timings log
42e30cf
42e30cf
@yalokin-jiminny
JY-20701
JY-20701
| add cache queries to optimize performance
| add cache queries to optimize performance
417c62d
417c62d
@yalokin-jiminny
JY-20701
JY-20701
| remove extra findContactByConfigurationAndId query
| remove extra findContactByConfigurationAndId query
17 / 20 checks OK
e886aef
e886aef
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.07596409,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.12769353,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6787] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.13886672,"width":0.08344415,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.16041501,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.17158818,"width":0.19963431,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny MCP Connector - Product - Confluence","depth":4,"bounds":{"left":0.0,"top":0.19313647,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny MCP Connector - Product - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.20430966,"width":0.08294548,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":4,"bounds":{"left":0.0,"top":0.22585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.23703113,"width":0.15791224,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.2585794,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.2697526,"width":0.02144282,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":4,"bounds":{"left":0.0,"top":0.29130086,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20500] Batch initial sync for Salesforce - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.30247405,"width":0.08610372,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.32402235,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.33519554,"width":0.042719416,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.3567438,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.367917,"width":0.013131649,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.38946527,"width":0.07962101,"height":0.032721467},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.40063846,"width":0.1740359,"height":0.010774142},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.39664805,"width":0.007978723,"height":0.01915403},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.4237829,"width":0.07413564,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (31)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.054521278,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.004986702,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.16323139,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.16988032,"top":0.09936153,"width":0.029089095,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18085106,"top":0.10574621,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20162898,"top":0.09936153,"width":0.03025266,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21276596,"top":0.10574621,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23454122,"top":0.09936153,"width":0.022938829,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24534574,"top":0.10574621,"width":0.009142287,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (21)","depth":12,"bounds":{"left":0.2601396,"top":0.09936153,"width":0.069980055,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27194148,"top":0.10574621,"width":0.04255319,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31831783,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21","depth":14,"bounds":{"left":0.32130983,"top":0.113727055,"width":0.0048204786,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.32613033,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33277926,"top":0.09936153,"width":0.03125,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34391624,"top":0.10574621,"width":0.016788565,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.36668882,"top":0.09936153,"width":0.032081116,"height":0.026336791},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.3778258,"top":0.10574621,"width":0.01761968,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"bounds":{"left":0.09325133,"top":0.14365523,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.039228722,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"bounds":{"left":0.09325133,"top":0.1452514,"width":0.2159242,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"bounds":{"left":0.30917552,"top":0.1452514,"width":0.04055851,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"bounds":{"left":0.34973404,"top":0.1452514,"width":0.08261303,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"bounds":{"left":0.4323471,"top":0.1452514,"width":0.05219415,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.48454124,"top":0.1452514,"width":0.0013297872,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"bounds":{"left":0.98636967,"top":0.13886672,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Review requested","depth":15,"bounds":{"left":0.35006648,"top":0.2019154,"width":0.0003324468,"height":0.016759777},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Review requested","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.039893616,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.35006648,"top":0.20351157,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requested your review on this pull request.","depth":15,"bounds":{"left":0.3854721,"top":0.20351157,"width":0.09158909,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add your review","depth":14,"bounds":{"left":0.70212764,"top":0.19872306,"width":0.036901597,"height":0.022346368},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add your review","depth":16,"bounds":{"left":0.70511967,"top":0.20391062,"width":0.030917553,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.24221867,"width":0.26230052,"height":0.031923383},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701 | Reschedule HubSpot Sync Objects","depth":14,"bounds":{"left":0.33776596,"top":0.24301676,"width":0.2122673,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.55269283,"top":0.24301676,"width":0.006482713,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11989","depth":15,"bounds":{"left":0.55917555,"top":0.24301676,"width":0.028922873,"height":0.030327214},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.5894282,"top":0.24541101,"width":0.010638298,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"bounds":{"left":0.6569149,"top":0.24860336,"width":0.055518616,"height":0.025538707},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"bounds":{"left":0.66921544,"top":0.254589,"width":0.038896278,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.24860336,"width":0.02825798,"height":0.025538707},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.254589,"width":0.011635638,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.34840426,"top":0.28651237,"width":0.011968086,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.36702126,"top":0.28332004,"width":0.034075797,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.36702126,"top":0.2849162,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 22 commits into","depth":15,"bounds":{"left":0.40242687,"top":0.2849162,"width":0.069148935,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.47290558,"top":0.282921,"width":0.018284574,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.47490028,"top":0.28611332,"width":0.014295213,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.49251994,"top":0.2849162,"width":0.009973404,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701-reschedule-HubSpot-processing","depth":16,"bounds":{"left":0.50382316,"top":0.282921,"width":0.09524601,"height":0.017557861},"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":17,"bounds":{"left":0.50581783,"top":0.28611332,"width":0.09125665,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.60039896,"top":0.28052673,"width":0.00930851,"height":0.022346368},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 949 additions & 97 deletions","depth":14,"bounds":{"left":0.7067819,"top":0.3367917,"width":0.019946808,"height":0.11412609},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (5)","depth":16,"bounds":{"left":0.33776596,"top":0.31883478,"width":0.057347074,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35139626,"top":0.32841182,"width":0.028091755,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38946143,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":18,"bounds":{"left":0.39245346,"top":0.32841182,"width":0.0028257978,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.39527926,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (22)","depth":16,"bounds":{"left":0.39511302,"top":0.31883478,"width":0.05069814,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.40874335,"top":0.32841182,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4401596,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":18,"bounds":{"left":0.4431516,"top":0.32841182,"width":0.005485372,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.44863698,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"bounds":{"left":0.44581118,"top":0.31883478,"width":0.04504654,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.45944148,"top":0.32841182,"width":0.015957447,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.48520613,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"bounds":{"left":0.48819813,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.49119017,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (11)","depth":16,"bounds":{"left":0.49085772,"top":0.31883478,"width":0.060339097,"height":0.031923383},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.50448805,"top":0.32841182,"width":0.029753989,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.5455452,"top":0.32841182,"width":0.0029920214,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11","depth":18,"bounds":{"left":0.54853725,"top":0.32841182,"width":0.0043218085,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.55285907,"top":0.32841182,"width":0.0016622341,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.0003324468,"height":0.0007980846},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.36711892,"width":0.048204787,"height":0.023144454},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"bounds":{"left":0.33776596,"top":0.3643256,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.3651237,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago •","depth":14,"bounds":{"left":0.3620346,"top":0.3651237,"width":0.24135639,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":17,"bounds":{"left":0.3620346,"top":0.37310454,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.39744017,"top":0.37310454,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":15,"bounds":{"left":0.42436835,"top":0.3715084,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":17,"bounds":{"left":0.42436835,"top":0.37310454,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":17,"bounds":{"left":0.44930187,"top":0.37310454,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":17,"bounds":{"left":0.45262632,"top":0.3715084,"width":0.020279255,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":19,"bounds":{"left":0.45262632,"top":0.37310454,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20701","depth":16,"bounds":{"left":0.3620346,"top":0.40822026,"width":0.25731382,"height":0.017557861},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"bounds":{"left":0.3620346,"top":0.4086193,"width":0.015791224,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20701","depth":17,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":18,"bounds":{"left":0.3778258,"top":0.4086193,"width":0.026263298,"height":0.016759777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Casus:","depth":16,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Casus:","depth":17,"bounds":{"left":0.3620346,"top":0.44493216,"width":0.015292553,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"We want more fluent curve of the jobs distribution (no spikes) in","depth":18,"bounds":{"left":0.3700133,"top":0.47326416,"width":0.13879654,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm-sync","depth":19,"bounds":{"left":0.51047206,"top":0.47525936,"width":0.018949468,"height":0.011572227},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"queue","depth":18,"bounds":{"left":0.53108376,"top":0.47326416,"width":0.01462766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times","depth":18,"bounds":{"left":0.3700133,"top":0.49281725,"width":0.24750665,"height":0.030726258},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.25731382,"height":0.01396648},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.3620346,"top":0.5442937,"width":0.021110373,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Remove HubSpot from SyncObjects","depth":18,"bounds":{"left":0.3700133,"top":0.5726257,"width":0.07712766,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Create separate HubSpotSyncObjects that runs each 5 min instead of each 30","depth":18,"bounds":{"left":0.3700133,"top":0.59217876,"width":0.16855054,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Extract common code","depth":18,"bounds":{"left":0.3700133,"top":0.6121309,"width":0.047041222,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Improve processing Webhooks jobs distribution","depth":18,"bounds":{"left":0.3700133,"top":0.63168395,"width":0.101894945,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Optimize ImportOpportunityBatch:","depth":18,"bounds":{"left":0.3700133,"top":0.65163606,"width":0.07396942,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cache OpportunitySyncableFields, OwnerProfiles, RecordType","depth":20,"bounds":{"left":0.37799203,"top":0.6683959,"width":0.13347739,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"remove findContactByConfigurationAndId - Contact ID already exist","depth":20,"bounds":{"left":0.37799203,"top":0.68834794,"width":0.1456117,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"add timings log","depth":20,"bounds":{"left":0.37799203,"top":0.70790106,"width":0.032912236,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.73623306,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":13,"bounds":{"left":0.33776596,"top":0.7960894,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.79688746,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"bounds":{"left":0.5934175,"top":0.80606544,"width":0.012965426,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"yalokin-jiminny commented 4 days ago","depth":13,"bounds":{"left":0.3620346,"top":0.79688746,"width":0.22240691,"height":0.02952913},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":15,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":16,"bounds":{"left":0.3620346,"top":0.80486834,"width":0.034075797,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.39744017,"top":0.80486834,"width":0.025598405,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.42436835,"top":0.8032721,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.42436835,"top":0.80486834,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.3620346,"top":0.8415802,"width":0.019115692,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.3620346,"top":0.86951315,"width":0.008643617,"height":0.0207502},"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"react with eyes","depth":14,"bounds":{"left":0.37200797,"top":0.86951315,"width":0.013796543,"height":0.0207502},"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"👀","depth":16,"bounds":{"left":0.37416887,"top":0.8747007,"width":0.004155585,"height":0.012769354},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.38098404,"top":0.8747007,"width":0.0018284575,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":13,"bounds":{"left":0.33776596,"top":0.9293695,"width":0.013297873,"height":0.031923383},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.61136967,"top":0.9301676,"width":0.007978723,"height":0.02952913},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"claude bot commented 4 days ago •","depth":13,"bounds":{"left":0.3620346,"top":0.9301676,"width":0.24135639,"height":0.029928172},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"claude","depth":15,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"claude","depth":16,"bounds":{"left":0.3620346,"top":0.93814844,"width":0.014960106,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"bot","depth":16,"bounds":{"left":0.3804854,"top":0.9397446,"width":0.006482713,"height":0.011971269},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.390625,"top":0.93814844,"width":0.02543218,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"4 days ago","depth":14,"bounds":{"left":0.41738698,"top":0.9365523,"width":0.023603724,"height":0.016759777},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 days ago","depth":16,"bounds":{"left":0.41738698,"top":0.93814844,"width":0.023603724,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"•","depth":16,"bounds":{"left":0.44232047,"top":0.93814844,"width":0.0021609042,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"edited","depth":16,"bounds":{"left":0.44581118,"top":0.9365523,"width":0.020113032,"height":0.016759777},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"edited","depth":18,"bounds":{"left":0.44581118,"top":0.93814844,"width":0.014793883,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Claude finished","depth":18,"bounds":{"left":0.3620346,"top":0.97525936,"width":0.036070477,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":18,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@yalokin-jiminny","depth":19,"bounds":{"left":0.39810506,"top":0.97525936,"width":0.038397606,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s task in 1m 59s","depth":18,"bounds":{"left":0.43650267,"top":0.97525936,"width":0.03756649,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"——","depth":17,"bounds":{"left":0.47406915,"top":0.97525936,"width":0.010638298,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View job","depth":17,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View job","depth":18,"bounds":{"left":0.48470744,"top":0.97525936,"width":0.018284574,"height":0.013567438},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":16,"bounds":{"left":0.3620346,"top":1.0,"width":0.25731382,"height":-0.0315243},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Review","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.03523936,"height":-0.031923413},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall the approach is clean — extracting the shared logic into","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.13696809,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsCommandTrait","depth":18,"bounds":{"left":0.5006649,"top":1.0,"width":0.0546875,"height":-0.065043926},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is a good call, and the scheduling design (offset cron at","depth":17,"bounds":{"left":0.3620346,"top":1.0,"width":0.24335106,"height":-0.06304872},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":01","depth":18,"bounds":{"left":0.43567154,"top":1.0,"width":0.0071476065,"height":-0.08180368},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to avoid spikes) makes sense. I found two bugs and a few minor points.","depth":17,"bounds":{"left":0.44448137,"top":1.0,"width":0.15425532,"height":-0.07980847},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Bugs","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bugs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Null dereference on","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:49-51","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"$team->crm","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"can be null if the team has no CRM configuration record. Accessing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->provider","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on null will throw a fatal error. The existing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job handles this by using","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"??","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$crmConfig->provider ?? 'unknown'","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). The new job needs a null guard here:","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"===","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"||","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"!== Configuration::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROVIDER_HUBSPOT","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"logger","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"warning","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[SyncHubspotObjects] Team has no HubSpot CRM config","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", [","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"team","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=>","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]);","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"return","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";\n}","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$lastSyncedAt","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is nullable but","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"requires","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Jobs/Crm/SyncHubspotObjects.php:85,88,116","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmConfig","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"last_synced_at","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// can be null on first run","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// ...","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"private","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"function","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncHubspotCrmObjects(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HubspotInterface","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crmService","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"lastSyncedAt","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"void","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"With","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"declare(strict_types=1)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", passing","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-typed parameter throws a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TypeError","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". A team that has never synced before will crash on the first run. The parameter should be","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"?Carbon","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(and","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"syncOpportunities","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should handle null), or provide a fallback like","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Carbon::createFromTimestamp(0)","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fix this →","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fix this →","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Minor Issues","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Minor Issues","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Double call to","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix()","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"=","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() ?","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"getLogPrefix","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"() .","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"''","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":";","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"getLogPrefix()","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is called twice. Trivial but unnecessary — capture the result in a variable first.","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4.","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"command doesn't guard HubSpot when","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{team}","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"argument is given —","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/SyncObjects.php:40-41","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") {","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teams","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[] = Team::","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"idOrUuId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"teamId","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":");","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"// no provider check","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Running","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-objects <hubspot-team-id>","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dispatches a","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjectsJob","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Design Notes","depth":16,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Design Notes","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Scheduler placement","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"crm:sync-hubspot-objects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is placed inside","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"scheduleEveryFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"but uses a manual cron","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead of","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"->everyFiveMinutes()","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQS visibility timeout","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncHubspotObjects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"uses 1 hour (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"60 * 60","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") vs","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncObjects","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"'s 6 hours (","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"60 * 60 * 6","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"). Appropriate given the more frequent, presumably lighter runs.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stagger delay","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20553-debug-crm-sync-delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f6c0ec3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f6c0ec3","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"5928f60","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5928f60","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yesterday","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yesterday","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"3d7234a","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3d7234a","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"1d10796","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1d10796","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yesterday","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yesterday","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Base automatically changed from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20553-debug-crm-sync-delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"master","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"19 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"19 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20701-reschedule-HubSpot-processing","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch from","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fec25f3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fec25f3","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f0119c9","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f0119c9","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Compare","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"19 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"19 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"15 hours ago","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15 hours ago","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| remove HubSpot from 30 min scheduler","depth":14,"help_text":"JY-20701 | remove HubSpot from 30 min scheduler","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| remove HubSpot from 30 min scheduler","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2f38f13","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2f38f13","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| Add SyncHubspotObjects Job","depth":14,"help_text":"JY-20701 | Add SyncHubspotObjects Job","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| Add SyncHubspotObjects Job","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"50b0e20","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"50b0e20","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| Add HubSpot sync objects scheduler each 5 min","depth":14,"help_text":"JY-20701 | Add HubSpot sync objects scheduler each 5 min","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| Add HubSpot sync objects scheduler each 5 min","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"ea92c0f","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ea92c0f","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| extract duplicated code","depth":14,"help_text":"JY-20701 | extract duplicated code","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| extract duplicated code","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"7ecdad3","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"7ecdad3","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| cs","depth":14,"help_text":"JY-20701 | cs","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| cs","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"525cbb2","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"525cbb2","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| safe crm check","depth":14,"help_text":"JY-20701 | safe crm check","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| safe crm check","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"35de0d5","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"35de0d5","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| null on first-time sync","depth":14,"help_text":"JY-20701 | null on first-time sync","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| null on first-time sync","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"ac1f89e","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ac1f89e","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| minor improvement","depth":14,"help_text":"JY-20701 | minor improvement","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| minor improvement","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"bab377e","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"bab377e","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve filter by team","depth":14,"help_text":"JY-20701 | improve filter by team","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve filter by team","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f39dc56","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f39dc56","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add scheduler comment","depth":14,"help_text":"JY-20701 | add scheduler comment","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add scheduler comment","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f2109d7","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f2109d7","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve delays","depth":14,"help_text":"JY-20701 | improve delays","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"f929670","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"f929670","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add tests","depth":14,"help_text":"JY-20701 | add tests","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add tests","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"9691864","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"9691864","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| improve webhook delays","depth":14,"help_text":"JY-20701 | improve webhook delays","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| improve webhook delays","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"a3a308f","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"a3a308f","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| update tests","depth":14,"help_text":"JY-20701 | update tests","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| update tests","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"64251d2","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"64251d2","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add OpportunitySyncTrait timings log","depth":14,"help_text":"JY-20701 | add OpportunitySyncTrait timings log","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add OpportunitySyncTrait timings log","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"42e30cf","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"42e30cf","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| add cache queries to optimize performance","depth":14,"help_text":"JY-20701 | add cache queries to optimize performance","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| add cache queries to optimize performance","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"417c62d","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"417c62d","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":12,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20701","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20701","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"| remove extra findContactByConfigurationAndId query","depth":14,"help_text":"JY-20701 | remove extra findContactByConfigurationAndId query","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| remove extra findContactByConfigurationAndId query","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"17 / 20 checks OK","depth":14,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"e886aef","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"e886aef","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"yalokin-jiminny","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"yalokin-jiminny","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"force-pushed","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"force-pushed","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-777375747648610541
|
8088980339503070537
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 2 Q2 - Platform Team - Scrum Board Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
[SRD-6787] Issue with reconnecting Zoho - Jira
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
JY-20698 handle failed field sync on playbook import activity types by LakyLak · Pull Request #11988 · jiminny/app
Jiminny MCP Connector - Product - Confluence
Jiminny MCP Connector - Product - Confluence
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
[JY-20676] Notify the user if a Panorama prompts is deleted but is used in AJ Report - Jira
Jiminny Mail
Jiminny Mail
[JY-20500] Batch initial sync for Salesforce - Jira
[JY-20500] Batch initial sync for Salesforce - Jira
Feed — jiminny — Sentry
Feed — jiminny — Sentry
Jiminny
Jiminny
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
JY-20701 | Reschedule HubSpot Sync Objects by yalokin-jiminny · Pull Request #11989 · jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
Issues(g then i)
Pull requests
Repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (31)
Pull requests
(
31
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (21)
Security and quality
(
21
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
Review requested
Review requested
yalokin-jiminny
yalokin-jiminny
requested your review on this pull request.
Add your review
Add your review
JY-20701 | Reschedule HubSpot Sync Objects #11989 Edit title
JY-20701 | Reschedule HubSpot Sync Objects
#
11989
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
yalokin-jiminny
yalokin-jiminny
wants to merge 22 commits into
master
master
from
JY-20701-reschedule-HubSpot-processing
JY-20701-reschedule-HubSpot-processing
Copy head branch name to clipboard
Lines changed: 949 additions & 97 deletions
Conversation (5)
Conversation
(
5
)
Commits (22)
Commits
(
22
)
Checks (3)
Checks
(
3
)
Files changed (11)
Files changed
(
11
)
Conversation
Conversation
@yalokin-jiminny
Show options
yalokin-jiminny commented 4 days ago •
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
•
edited
edited
JIRA: JY-20701
JIRA:
JY-20701
JY-20701
Casus:
Casus:
We want more fluent curve of the jobs distribution (no spikes) in
crm-sync
queue
The more frequent HubSpot processing will help to have less auto-scaling / down-scaling events, thus reducing the jobs wait times
Changes:
Changes:
Remove HubSpot from SyncObjects
Create separate HubSpotSyncObjects that runs each 5 min instead of each 30
Extract common code
Improve processing Webhooks jobs distribution
Optimize ImportOpportunityBatch:
cache OpportunitySyncableFields, OwnerProfiles, RecordType
remove findContactByConfigurationAndId - Contact ID already exist
add timings log
Add or remove reactions
@yalokin-jiminny
Show options
Author
yalokin-jiminny commented 4 days ago
yalokin-jiminny
yalokin-jiminny
commented
4 days ago
4 days ago
@claude
@claude
Add or remove reactions
react with eyes
👀
1
@claude
Show options
claude bot commented 4 days ago •
claude
claude
bot
commented
4 days ago
4 days ago
•
edited
edited
Claude finished
@yalokin-jiminny
@yalokin-jiminny
's task in 1m 59s
——
View job
View job
Code Review
Code Review
Overall the approach is clean — extracting the shared logic into
SyncObjectsCommandTrait
is a good call, and the scheduling design (offset cron at
:01
to avoid spikes) makes sense. I found two bugs and a few minor points.
Bugs
Bugs
1. Null dereference on
$crmConfig
—
app/Jobs/Crm/SyncHubspotObjects.php:49-51
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
Copy
$team->crm
can be null if the team has no CRM configuration record. Accessing
->provider
on null will throw a fatal error. The existing
SyncObjects
job handles this by using
??
(
$crmConfig->provider ?? 'unknown'
). The new job needs a null guard here:
$
crmConfig
=
$
team
->
crm
;
if
(
$
crmConfig
===
null
||
$
crmConfig
->
provider
!== Configuration::
PROVIDER_HUBSPOT
) {
$
logger
->
warning
(
'
[SyncHubspotObjects] Team has no HubSpot CRM config
'
, [
'
team
'
=>
$
this
->
teamId
]);
return
;
}
Copy
Fix this →
Fix this →
2.
$lastSyncedAt
is nullable but
syncHubspotCrmObjects
requires
Carbon
—
app/Jobs/Crm/SyncHubspotObjects.php:85,88,116
$
lastSyncedAt
=
$
crmConfig
->
last_synced_at
;
// can be null on first run
.
.
.
$
this
->
syncHubspotCrmObjects
(
$
crmService
,
$
lastSyncedAt
);
// ...
private
function
syncHubspotCrmObjects(
HubspotInterface
$
crmService
,
Carbon
$
lastSyncedAt
):
void
Copy
With
declare(strict_types=1)
, passing
null
to a
Carbon
-typed parameter throws a
TypeError
. A team that has never synced before will crash on the first run. The parameter should be
?Carbon
(and
syncOpportunities
should handle null), or provide a fallback like
Carbon::createFromTimestamp(0)
.
Fix this →
Fix this →
Minor Issues
Minor Issues
3. Double call to
getLogPrefix()
—
app/Console/Commands/Crm/Traits/SyncObjectsCommandTrait.php:52
$
prefix
=
$
this
->
getLogPrefix
() ?
$
this
->
getLogPrefix
() .
'
'
:
''
;
Copy
getLogPrefix()
is called twice. Trivial but unnecessary — capture the result in a variable first.
4.
SyncObjects
command doesn't guard HubSpot when
{team}
argument is given —
app/Console/Commands/Crm/SyncObjects.php:40-41
if
(
$
teamId
) {
$
teams
[] = Team::
idOrUuId
(
$
teamId
);
// no provider check
Copy
Running
crm:sync-objects <hubspot-team-id>
dispatches a
SyncObjectsJob
for a HubSpot team, which now silently warns and returns. From an operator perspective this is confusing — the command appears to accept the team but does nothing. Worth adding a provider check and a console error message pointing to
crm:sync-hubspot-objects
.
Design Notes
Design Notes
Scheduler placement
:
crm:sync-hubspot-objects
is placed inside
scheduleEveryFiveMinutes()
but uses a manual cron
'1,6,11,16,21,26,31,36,41,46,51,56 * * * *'
instead of
->everyFiveMinutes()
. The 1-minute offset is intentional (good thinking to stagger from the other jobs), but it's slightly inconsistent with the method's name. A comment explaining the offset would help future readers.
SQS visibility timeout
:
SyncHubspotObjects
uses 1 hour (
60 * 60
) vs
SyncObjects
's 6 hours (
60 * 60 * 6
). Appropriate given the more frequent, presumably lighter runs.
Stagger delay
: HubSpot uses 1s between teams vs 2s for general sync. Fine for 5-min runs, though worth monitoring if the number of HubSpot teams grows large enough that the last job barely fits within the 5-minute window.
Add or remove reactions
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20553-debug-crm-sync-delays
branch from
f6c0ec3
f6c0ec3
to
5928f60
5928f60
Compare
Compare
yesterday
yesterday
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
3d7234a
3d7234a
to
1d10796
1d10796
Compare
Compare
yesterday
yesterday
Base automatically changed from
JY-20553-debug-crm-sync-delays
to
master
19 hours ago
19 hours ago
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed
the
JY-20701-reschedule-HubSpot-processing
branch from
fec25f3
fec25f3
to
f0119c9
f0119c9
Compare
Compare
19 hours ago
19 hours ago
yalokin-jiminny
yalokin-jiminny
added
17
commits
15 hours ago
15 hours ago
@yalokin-jiminny
JY-20701
JY-20701
| remove HubSpot from 30 min scheduler
| remove HubSpot from 30 min scheduler
2f38f13
2f38f13
@yalokin-jiminny
JY-20701
JY-20701
| Add SyncHubspotObjects Job
| Add SyncHubspotObjects Job
50b0e20
50b0e20
@yalokin-jiminny
JY-20701
JY-20701
| Add HubSpot sync objects scheduler each 5 min
| Add HubSpot sync objects scheduler each 5 min
ea92c0f
ea92c0f
@yalokin-jiminny
JY-20701
JY-20701
| extract duplicated code
| extract duplicated code
7ecdad3
7ecdad3
@yalokin-jiminny
JY-20701
JY-20701
| cs
| cs
525cbb2
525cbb2
@yalokin-jiminny
JY-20701
JY-20701
| safe crm check
| safe crm check
35de0d5
35de0d5
@yalokin-jiminny
JY-20701
JY-20701
| null on first-time sync
| null on first-time sync
ac1f89e
ac1f89e
@yalokin-jiminny
JY-20701
JY-20701
| minor improvement
| minor improvement
bab377e
bab377e
@yalokin-jiminny
JY-20701
JY-20701
| improve filter by team
| improve filter by team
f39dc56
f39dc56
@yalokin-jiminny
JY-20701
JY-20701
| add scheduler comment
| add scheduler comment
f2109d7
f2109d7
@yalokin-jiminny
JY-20701
JY-20701
| improve delays
| improve delays
f929670
f929670
@yalokin-jiminny
JY-20701
JY-20701
| add tests
| add tests
9691864
9691864
@yalokin-jiminny
JY-20701
JY-20701
| improve webhook delays
| improve webhook delays
a3a308f
a3a308f
@yalokin-jiminny
JY-20701
JY-20701
| update tests
| update tests
64251d2
64251d2
@yalokin-jiminny
JY-20701
JY-20701
| add OpportunitySyncTrait timings log
| add OpportunitySyncTrait timings log
42e30cf
42e30cf
@yalokin-jiminny
JY-20701
JY-20701
| add cache queries to optimize performance
| add cache queries to optimize performance
417c62d
417c62d
@yalokin-jiminny
JY-20701
JY-20701
| remove extra findContactByConfigurationAndId query
| remove extra findContactByConfigurationAndId query
17 / 20 checks OK
e886aef
e886aef
@yalokin-jiminny
yalokin-jiminny
yalokin-jiminny
force-pushed
force-pushed...
|
60911
|
|
45595
|
964
|
13
|
2026-04-17T09:53:20.476781+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776419600476_m2.jpg...
|
Slack
|
Nikolay Nikolov (DM) - Jiminny Inc - 3 new items - Nikolay Nikolov (DM) - Jiminny Inc - 3 new items - Slack...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
jiminny_social
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Files
Files
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Nikolov
Apr 8th at 6:31:19 PM
6:31 PM
https://github.com/jiminny/app/pull/11918
https://github.com/jiminny/app/pull/11918
#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event
#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event
JIRA:
SRD-6775
SRD-6775
|
JY-20630
JY-20630
Deployment notes:
• None
Changes:
• Return false to skip remote engagement update with a secondary activity data
Comments
3
jiminny/app
jiminny/app
|
Apr 8th
|
Added by
GitHub
GitHub
Jump to date
Nikolay Nikolov
Apr 9th at 10:50:09 AM
10:50 AM
Oautha mi изгърмя:
https://github.com/jiminny/app/pull/11926
https://github.com/jiminny/app/pull/11926
#11926 SRD-6771 | JY-20622 | Fix OAuth
#11926 SRD-6771 | JY-20622 | Fix OAuth
JIRA:
SRD-6771
SRD-6771
|
JY-20622
JY-20622
Deployment notes:
• None
Comments
3
jiminny/app
jiminny/app
|
Apr 9th
|
Added by
GitHub
GitHub
Lukas Kovalik
Apr 9th at 10:53:23 AM
10:53 AM
може и да ти умре по време на exec
Apr 9th at 10:53:32 AM
10:53
ако трае дълго
Apr 9th at 10:53:43 AM
10:53
но май не бяха толкова много
Nikolay Nikolov
Apr 9th at 10:53:50 AM
10:53 AM
не, то не беше сетнато изобщо
Apr 9th at 10:53:56 AM
10:53
и после прави единични упдейти
Apr 9th at 10:54:37 AM
10:54
196 са само
1 reaction, react with +1 emoji
1
Add reaction…
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Nikolay Nikolov
Apr 9th at 11:37:11 AM
11:37 AM
Тия имат по няколко сделки на активити: restored-deal-associations-2026-04-09.csv
https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675
https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675
Comment by Nikolay Nikolov on a Bug in
Ready for customer
Here are 196 successfully restored activity-to-deal associations:
• 196 activities
• 236 deals
Comment
Comment
More actions...
Added by
Jira Cloud
Jira Cloud
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"bounds":{"left":0.00546875,"top":0.05486111,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"bounds":{"left":0.00546875,"top":0.09097222,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"bounds":{"left":0.00546875,"top":0.12708333,"width":0.0125,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.026953125,"top":0.048611112,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.03125,"top":0.08125,"width":0.012109375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.026953125,"top":0.09583333,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"bounds":{"left":0.032421876,"top":0.12847222,"width":0.009765625,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"bounds":{"left":0.026953125,"top":0.14305556,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"bounds":{"left":0.0296875,"top":0.17569445,"width":0.015234375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"bounds":{"left":0.026953125,"top":0.19027779,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"bounds":{"left":0.0328125,"top":0.22291666,"width":0.008984375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"bounds":{"left":0.026953125,"top":0.2375,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"bounds":{"left":0.03203125,"top":0.2701389,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"bounds":{"left":0.026953125,"top":0.2847222,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"bounds":{"left":0.03203125,"top":0.31736112,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"bounds":{"left":0.06679688,"top":0.0875,"width":0.022265624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"bounds":{"left":0.06679688,"top":0.10694444,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"bounds":{"left":0.06679688,"top":0.12638889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"bounds":{"left":0.06679688,"top":0.14583333,"width":0.034375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"bounds":{"left":0.06679688,"top":0.16527778,"width":0.028515626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":22,"bounds":{"left":0.07304688,"top":0.24722221,"width":0.0515625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":22,"bounds":{"left":0.07304688,"top":0.26666668,"width":0.05234375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":22,"bounds":{"left":0.07304688,"top":0.3125,"width":0.026171874,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":22,"bounds":{"left":0.07304688,"top":0.33194444,"width":0.014453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":22,"bounds":{"left":0.07304688,"top":0.3513889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":22,"bounds":{"left":0.07304688,"top":0.37083334,"width":0.040625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":22,"bounds":{"left":0.07304688,"top":0.39027777,"width":0.032421876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"bounds":{"left":0.07304688,"top":0.4097222,"width":0.03125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":22,"bounds":{"left":0.07304688,"top":0.42916667,"width":0.02265625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"general","depth":22,"bounds":{"left":0.07304688,"top":0.4486111,"width":0.019140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":22,"bounds":{"left":0.07304688,"top":0.46805555,"width":0.034765624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":22,"bounds":{"left":0.07304688,"top":0.4875,"width":0.02734375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":22,"bounds":{"left":0.07304688,"top":0.5069444,"width":0.041015625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":22,"bounds":{"left":0.07304688,"top":0.5263889,"width":0.0453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"random","depth":22,"bounds":{"left":0.07304688,"top":0.54583335,"width":0.019921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":22,"bounds":{"left":0.07304688,"top":0.56527776,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":22,"bounds":{"left":0.07304688,"top":0.5847222,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"support","depth":22,"bounds":{"left":0.07304688,"top":0.6041667,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":22,"bounds":{"left":0.07304688,"top":0.6236111,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":22,"bounds":{"left":0.07304688,"top":0.64305556,"width":0.053125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny_social","depth":22,"bounds":{"left":0.07304688,"top":0.6625,"width":0.0359375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":22,"bounds":{"left":0.07304688,"top":0.7083333,"width":0.040234376,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"bounds":{"left":0.07304688,"top":0.7277778,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"bounds":{"left":0.11679687,"top":0.7277778,"width":0.0078125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":22,"bounds":{"left":0.11992188,"top":0.7277778,"width":0.016796876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"bounds":{"left":0.13632813,"top":0.7430556,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"bounds":{"left":0.13632813,"top":0.7430556,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":22,"bounds":{"left":0.07304688,"top":0.74722224,"width":0.04140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":22,"bounds":{"left":0.07304688,"top":0.76666665,"width":0.033984374,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":22,"bounds":{"left":0.07304688,"top":0.7861111,"width":0.03125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":22,"bounds":{"left":0.07304688,"top":0.8055556,"width":0.037890624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"bounds":{"left":0.07304688,"top":0.825,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":22,"bounds":{"left":0.07304688,"top":0.84444445,"width":0.009375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"bounds":{"left":0.07304688,"top":0.86388886,"width":0.044921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":22,"bounds":{"left":0.07304688,"top":0.9097222,"width":0.026171874,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":22,"bounds":{"left":0.07304688,"top":0.9291667,"width":0.014453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":22,"bounds":{"left":0.07304688,"top":0.94861114,"width":0.0359375,"height":0.0125},"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":17,"bounds":{"left":0.14335938,"top":0.07986111,"width":0.036328126,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":19,"bounds":{"left":0.15429688,"top":0.0875,"width":0.022265624,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":17,"bounds":{"left":0.18085937,"top":0.07986111,"width":0.024609376,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":19,"bounds":{"left":0.19179687,"top":0.0875,"width":0.010546875,"height":0.011111111},"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":17,"bounds":{"left":0.20703125,"top":0.07986111,"width":0.0125,"height":0.02638889},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"List","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.009375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01640625,"height":0.00069444446},"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":22,"bounds":{"left":0.28515625,"top":0.10069445,"width":0.06484375,"height":0.00069444446},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Nikolov","depth":23,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.0421875,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.20390625,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 8th at 6:31:19 PM","depth":23,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.01796875,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6:31 PM","depth":24,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.01796875,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"https://github.com/jiminny/app/pull/11918","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.111328125,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://github.com/jiminny/app/pull/11918","depth":25,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.111328125,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.2203125,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.2203125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"JIRA:","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"SRD-6775","depth":25,"bounds":{"left":0.18320313,"top":0.10069445,"width":0.027734375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"SRD-6775","depth":26,"bounds":{"left":0.18320313,"top":0.10069445,"width":0.027734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.21054688,"top":0.10069445,"width":0.004296875,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"JY-20630","depth":25,"bounds":{"left":0.21445313,"top":0.10069445,"width":0.02578125,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"JY-20630","depth":26,"bounds":{"left":0.21445313,"top":0.10069445,"width":0.02578125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Deployment notes:","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.049609374,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"• None","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.019140625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Changes:","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.023828125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"• Return false to skip remote engagement update with a secondary activity data","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.2046875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Comments","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.028515626,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"jiminny/app","depth":25,"bounds":{"left":0.17617187,"top":0.10069445,"width":0.02421875,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"jiminny/app","depth":26,"bounds":{"left":0.17617187,"top":0.10069445,"width":0.02421875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.2,"top":0.10069445,"width":0.00390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Apr 8th","depth":25,"bounds":{"left":0.20351562,"top":0.10069445,"width":0.016015625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.21914062,"top":0.10069445,"width":0.00390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Added by","depth":25,"bounds":{"left":0.22265625,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"GitHub","depth":25,"bounds":{"left":0.24335937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GitHub","depth":26,"bounds":{"left":0.24335937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":22,"bounds":{"left":0.2878906,"top":0.110416666,"width":0.059375,"height":0.019444445},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Nikolov","depth":23,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.0421875,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.20390625,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:50:09 AM","depth":23,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:50 AM","depth":24,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Oautha mi изгърмя:","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.053515624,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"https://github.com/jiminny/app/pull/11926","depth":24,"bounds":{"left":0.21523437,"top":0.10069445,"width":0.11171875,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://github.com/jiminny/app/pull/11926","depth":25,"bounds":{"left":0.21523437,"top":0.10069445,"width":0.11171875,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"#11926 SRD-6771 | JY-20622 | Fix OAuth","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.10976563,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#11926 SRD-6771 | JY-20622 | Fix OAuth","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.10976563,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"JIRA:","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"SRD-6771","depth":25,"bounds":{"left":0.18320313,"top":0.10069445,"width":0.027734375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"SRD-6771","depth":26,"bounds":{"left":0.18320313,"top":0.10069445,"width":0.027734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.21054688,"top":0.10069445,"width":0.004296875,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"JY-20622","depth":25,"bounds":{"left":0.21445313,"top":0.10069445,"width":0.02578125,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"JY-20622","depth":26,"bounds":{"left":0.21445313,"top":0.10069445,"width":0.02578125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Deployment notes:","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.049609374,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"• None","depth":25,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.019140625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Comments","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.028515626,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":26,"bounds":{"left":0.16835937,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"jiminny/app","depth":25,"bounds":{"left":0.17617187,"top":0.10069445,"width":0.02421875,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"jiminny/app","depth":26,"bounds":{"left":0.17617187,"top":0.10069445,"width":0.02421875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.2,"top":0.10069445,"width":0.00390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Apr 9th","depth":25,"bounds":{"left":0.20351562,"top":0.10069445,"width":0.016015625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"|","depth":25,"bounds":{"left":0.21914062,"top":0.10069445,"width":0.00390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Added by","depth":25,"bounds":{"left":0.22265625,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"GitHub","depth":25,"bounds":{"left":0.24335937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GitHub","depth":26,"bounds":{"left":0.24335937,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Lukas Kovalik","depth":23,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.036328126,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.20585938,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:53:23 AM","depth":23,"bounds":{"left":0.20898438,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:53 AM","depth":24,"bounds":{"left":0.20898438,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"може и да ти умре по време на exec","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.09804688,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:53:32 AM","depth":24,"bounds":{"left":0.146875,"top":0.10069445,"width":0.012109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:53","depth":25,"bounds":{"left":0.146875,"top":0.10069445,"width":0.012109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ако трае дълго","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.040625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:53:43 AM","depth":24,"bounds":{"left":0.146875,"top":0.10069445,"width":0.012109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:53","depth":25,"bounds":{"left":0.146875,"top":0.10069445,"width":0.012109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"но май не бяха толкова много","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.08125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Nikolay Nikolov","depth":23,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.0421875,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.20390625,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:53:50 AM","depth":23,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:53 AM","depth":24,"bounds":{"left":0.20703125,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"не, то не беше сетнато изобщо","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.084375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:53:56 AM","depth":24,"bounds":{"left":0.146875,"top":0.10555556,"width":0.012109375,"height":0.010416667},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:53","depth":25,"bounds":{"left":0.146875,"top":0.10555556,"width":0.012109375,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"и после прави единични упдейти","depth":24,"bounds":{"left":0.16210938,"top":0.103472225,"width":0.09023438,"height":0.0125},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 10:54:37 AM","depth":24,"bounds":{"left":0.146875,"top":0.12638889,"width":0.012109375,"height":0.010416667},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:54","depth":25,"bounds":{"left":0.146875,"top":0.12638889,"width":0.012109375,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"196 са само","depth":24,"bounds":{"left":0.16210938,"top":0.124305554,"width":0.032421876,"height":0.0125},"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":24,"bounds":{"left":0.16210938,"top":0.14097223,"width":0.016796876,"height":0.016666668},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":25,"bounds":{"left":0.17304687,"top":0.14375,"width":0.002734375,"height":0.010416667},"role_description":"text"},{"role":"AXButton","text":"Add reaction…","depth":24,"bounds":{"left":0.18007812,"top":0.14097223,"width":0.013671875,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"bounds":{"left":0.49140626,"top":0.10277778,"width":0.000390625,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Nikolov","depth":23,"bounds":{"left":0.16210938,"top":0.16597222,"width":0.0421875,"height":0.015277778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.20390625,"top":0.16736111,"width":0.003515625,"height":0.0125},"role_description":"text"},{"role":"AXLink","text":"Apr 9th at 11:37:11 AM","depth":23,"bounds":{"left":0.20703125,"top":0.16944444,"width":0.02109375,"height":0.010416667},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:37 AM","depth":24,"bounds":{"left":0.20703125,"top":0.16944444,"width":0.02109375,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"Тия имат по няколко сделки на активити: restored-deal-associations-2026-04-09.csv","depth":24,"bounds":{"left":0.16210938,"top":0.18263888,"width":0.22460938,"height":0.0125},"role_description":"text"},{"role":"AXLink","text":"https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675","depth":24,"bounds":{"left":0.16210938,"top":0.19791667,"width":0.19453125,"height":0.0125},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675","depth":25,"bounds":{"left":0.16210938,"top":0.19791667,"width":0.19453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Comment by Nikolay Nikolov on a Bug in","depth":26,"bounds":{"left":0.16835937,"top":0.21736111,"width":0.107421875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Ready for customer","depth":27,"bounds":{"left":0.27695313,"top":0.21944444,"width":0.051171876,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"Here are 196 successfully restored activity-to-deal associations:","depth":26,"bounds":{"left":0.16835937,"top":0.24097222,"width":0.16367188,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"• 196 activities","depth":26,"bounds":{"left":0.16835937,"top":0.25625,"width":0.03984375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"• 236 deals","depth":26,"bounds":{"left":0.16835937,"top":0.27152777,"width":0.030078124,"height":0.0125},"role_description":"text"},{"role":"AXButton","text":"Comment","depth":25,"bounds":{"left":0.16835937,"top":0.29375,"width":0.029296875,"height":0.019444445},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Comment","depth":27,"bounds":{"left":0.171875,"top":0.29722223,"width":0.022265624,"height":0.011805556},"role_description":"text"},{"role":"AXComboBox","text":"More actions...","depth":26,"bounds":{"left":0.20039062,"top":0.29375,"width":0.07460938,"height":0.019444445},"placeholder":"More actions...","role_description":"combo box","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Added by","depth":25,"bounds":{"left":0.16835937,"top":0.32152778,"width":0.020703126,"height":0.010416667},"role_description":"text"},{"role":"AXLink","text":"Jira Cloud","depth":25,"bounds":{"left":0.18867187,"top":0.32152778,"width":0.020703126,"height":0.010416667},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Jira Cloud","depth":26,"bounds":{"left":0.18867187,"top":0.32152778,"width":0.020703126,"height":0.010416667},"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"bounds":{"left":0.49140626,"top":0.15416667,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"bounds":{"left":0.49140626,"top":0.15416667,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"bounds":{"left":0.49140626,"top":0.15416667,"width":0.000390625,"height":0.022222223},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"bounds":{"left":0.49140626,"top":0.15416667,"width":0.000390625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1676716810545642114
|
8078984523256055820
|
visual_change
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
jiminny_social
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Files
Files
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Nikolov
Apr 8th at 6:31:19 PM
6:31 PM
https://github.com/jiminny/app/pull/11918
https://github.com/jiminny/app/pull/11918
#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event
#11918 SRD-6775 | JY-20630 | skip wrong engagement update when 2 activities has same calendar event
JIRA:
SRD-6775
SRD-6775
|
JY-20630
JY-20630
Deployment notes:
• None
Changes:
• Return false to skip remote engagement update with a secondary activity data
Comments
3
jiminny/app
jiminny/app
|
Apr 8th
|
Added by
GitHub
GitHub
Jump to date
Nikolay Nikolov
Apr 9th at 10:50:09 AM
10:50 AM
Oautha mi изгърмя:
https://github.com/jiminny/app/pull/11926
https://github.com/jiminny/app/pull/11926
#11926 SRD-6771 | JY-20622 | Fix OAuth
#11926 SRD-6771 | JY-20622 | Fix OAuth
JIRA:
SRD-6771
SRD-6771
|
JY-20622
JY-20622
Deployment notes:
• None
Comments
3
jiminny/app
jiminny/app
|
Apr 9th
|
Added by
GitHub
GitHub
Lukas Kovalik
Apr 9th at 10:53:23 AM
10:53 AM
може и да ти умре по време на exec
Apr 9th at 10:53:32 AM
10:53
ако трае дълго
Apr 9th at 10:53:43 AM
10:53
но май не бяха толкова много
Nikolay Nikolov
Apr 9th at 10:53:50 AM
10:53 AM
не, то не беше сетнато изобщо
Apr 9th at 10:53:56 AM
10:53
и после прави единични упдейти
Apr 9th at 10:54:37 AM
10:54
196 са само
1 reaction, react with +1 emoji
1
Add reaction…
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Nikolay Nikolov
Apr 9th at 11:37:11 AM
11:37 AM
Тия имат по няколко сделки на активити: restored-deal-associations-2026-04-09.csv
https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675
https://jiminny.atlassian.net/browse/SRD-6771?focusedCommentId=72675
Comment by Nikolay Nikolov on a Bug in
Ready for customer
Here are 196 successfully restored activity-to-deal associations:
• 196 activities
• 236 deals
Comment
Comment
More actions...
Added by
Jira Cloud
Jira Cloud
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
SlackFileEditViewJiminny ...DMs= Unreadse) Threads6 Huddles* Drafts & sent8 DirectoriesAchivityEh External connectionsFiles# Starred8 jiminny-x-integrati...platform-inner-teamMore# Channels# ai-chapter# alerts# backendcontlicion-clinia# curiosity lab# engineering# frontendi# general# infra-changes# jiminny-bg# platform-tickets# product_launchesac random* releases# sofia-office# support# thank-yous# the people of iimi..# jiminny_social &sDirect messages• Nikolay NiKolov3 Aneliya Angelova, ...e) Galva Dimitrovalo Stoyan Tanevgo Vasil vasilev. Nikolay Ivanov®. Aneliya AngelovaP. Ves. Steliyan Georgiev**:Appsfi Jira CloudToastHistoryWindowHelpQ Search Jiminny Ince. Nikolay Nikolov• MessagesC Filesи после прави единични упдейти196 са самоThursday, April 9th~)Nikolay Nikolov 11:37 AMТия имат по няколко сделки на активити: [URL_WITH_CREDENTIALS] в google с негоИзкарва ми бутон за Recoverнещо да не е станало ?Lukas Kovalik " 2:00 PMздрастия пак, че не разбрахHdllUdbo звbнmиаз посди пиколко Часа се мочихе нео но уже на редYou missed a huddle MISSED2:21PMNikolay Nikolov was in the huddle for 0m.Lukas Kovalik "" 3:17PMбях излязьл да сменя гумите, пиши ако ти трябвам ощеNikolay Nikolov 4:43 PMне, няма грижитова за репортите дали е проблем, че Стефка е писалаза S3Lukas Kovalik * 5:12 PMне знам, не съм го гледалутре ще го видяToday -INKOCY NIKOOV IALLAMInuios.meet2o02e.com/xox-oman-rknCi meet.google.comMeetReal-time meetings by Google. Using your browser, share your video, desktop, andeesentrons wiun icammares ane custoners.Message Nikolay NikolovAalibdf withrated IDs, butDes ltentional andrity.instead of -"Support Daily • in 2h7 m~ IEEre | gvarabes in requestG tokenCookiesG baseUrl> All variables100% [ Fri 17 Apr 12:53:19ö0 (Invite Upgrade @ CCLOaILI 2MXIZOINGMIOKOEWIAO...https://api.hubapi.comse ***INeW.•.nore aoout? Convert to draftGlobals Vault Tools & 000...
|
NULL
|
|
8633
|
166
|
13
|
2026-04-14T06:57:23.760141+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776149843760_m1.jpg...
|
Dia
|
Work: Meet - Daily - Pla…
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Nikolay Yankov (Presenting)
Nikolay Yankov (Presen Nikolay Yankov (Presenting)
Nikolay Yankov (Presenting)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Zoom in
Open in new window
Enter Full Screen
Stefka Stoyanova...
|
[{"role":"AXHeading","text" [{"role":"AXHeading","text":"Nikolay Yankov (Presenting)","depth":13,"bounds":{"left":0.045833334,"top":0.11,"width":0.125,"height":0.022222223},"role_description":"heading"},{"role":"AXStaticText","text":"Nikolay Yankov (Presenting)","depth":14,"bounds":{"left":0.045833334,"top":0.11111111,"width":0.125,"height":0.02},"role_description":"text"},{"role":"AXPopUpButton","text":"People","depth":15,"bounds":{"left":0.8826389,"top":0.09888889,"width":0.04097222,"height":0.04},"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":18,"bounds":{"left":0.91041666,"top":0.11,"width":0.0048611113,"height":0.016666668},"role_description":"text"},{"role":"AXPopUpButton","text":"Take notes with Gemini","depth":15,"bounds":{"left":0.9291667,"top":0.09888889,"width":0.025,"height":0.04},"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Take notes with Gemini","depth":19,"bounds":{"left":0.93194443,"top":0.11,"width":0.022222223,"height":0.016666668},"role_description":"text"},{"role":"AXStaticText","text":"Gemini","depth":19,"bounds":{"left":0.9625,"top":0.11,"width":0.022222223,"height":0.016666668},"role_description":"text"},{"role":"AXButton","text":"Gemini","depth":18,"bounds":{"left":0.9604167,"top":0.1,"width":0.023611112,"height":0.037777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Zoom in","depth":14,"bounds":{"left":0.6201389,"top":0.7922222,"width":0.027777778,"height":0.044444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in new window","depth":15,"bounds":{"left":0.65347224,"top":0.7922222,"width":0.027777778,"height":0.044444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Enter Full Screen","depth":15,"bounds":{"left":0.68680555,"top":0.7922222,"width":0.027777778,"height":0.044444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":17,"bounds":{"left":0.7430556,"top":0.36666667,"width":0.08888889,"height":0.022222223},"role_description":"text"}]...
|
6319403837425361321
|
8077623044035967784
|
click
|
accessibility
|
NULL
|
Nikolay Yankov (Presenting)
Nikolay Yankov (Presen Nikolay Yankov (Presenting)
Nikolay Yankov (Presenting)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Zoom in
Open in new window
Enter Full Screen
Stefka Stoyanova...
|
8627
|
|
8634
|
167
|
29
|
2026-04-14T06:57:23.760115+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776149843760_m2.jpg...
|
Dia
|
Work: Meet - Daily - Pla…
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Nikolay Yankov (Presenting)
Nikolay Yankov (Presen Nikolay Yankov (Presenting)
Nikolay Yankov (Presenting)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Zoom in
Open in new window
Enter Full Screen
Stefka Stoyanova...
|
[{"role":"AXHeading","text" [{"role":"AXHeading","text":"Nikolay Yankov (Presenting)","depth":13,"bounds":{"left":0.2589844,"top":1.0,"width":0.0703125,"height":-0.068750024},"role_description":"heading"},{"role":"AXStaticText","text":"Nikolay Yankov (Presenting)","depth":14,"bounds":{"left":0.2589844,"top":1.0,"width":0.0703125,"height":-0.06944442},"role_description":"text"},{"role":"AXPopUpButton","text":"People","depth":15,"bounds":{"left":0.7296875,"top":1.0,"width":0.023046875,"height":-0.061805606},"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":18,"bounds":{"left":0.7453125,"top":1.0,"width":0.002734375,"height":-0.068750024},"role_description":"text"},{"role":"AXPopUpButton","text":"Take notes with Gemini","depth":15,"bounds":{"left":0.7558594,"top":1.0,"width":0.0140625,"height":-0.061805606},"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Take notes with Gemini","depth":19,"bounds":{"left":0.75742185,"top":1.0,"width":0.0125,"height":-0.068750024},"role_description":"text"},{"role":"AXStaticText","text":"Gemini","depth":19,"bounds":{"left":0.7746094,"top":1.0,"width":0.0125,"height":-0.068750024},"role_description":"text"},{"role":"AXButton","text":"Gemini","depth":18,"bounds":{"left":0.7734375,"top":1.0,"width":0.01328125,"height":-0.0625},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Zoom in","depth":14,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in new window","depth":15,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Enter Full Screen","depth":15,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":17,"role_description":"text"}]...
|
6319403837425361321
|
8077623044035967784
|
click
|
accessibility
|
NULL
|
Nikolay Yankov (Presenting)
Nikolay Yankov (Presen Nikolay Yankov (Presenting)
Nikolay Yankov (Presenting)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Zoom in
Open in new window
Enter Full Screen
Stefka Stoyanova...
|
8632
|