|
11014
|
216
|
41
|
2026-04-14T09:08:27.844377+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157707844_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:278Today ~...
|
NULL
|
6062814761654282972
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:278Today ~...
|
11012
|
|
11016
|
216
|
42
|
2026-04-14T09:08:30.533488+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157710533_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:308Today ~...
|
NULL
|
761261292420667706
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:308Today ~...
|
NULL
|
|
11020
|
NULL
|
0
|
2026-04-14T09:08:39.627305+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157719627_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:3986 0Today ~...
|
NULL
|
-8257480601626042446
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 52 m100% <47Tue 14 Apr 12:08:3986 0Today ~...
|
11016
|
|
11023
|
218
|
0
|
2026-04-14T09:09:00.179423+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157740179_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
EventSubscriber, folder
FilterDefinition, folder
Service
ActivityApiSearch.php, class
ActivitySearch.php, class
UserOptionsByGroup.php, class
AbstractStageFilterDefinition.php, abstract class
ActivitySearchServiceProvider.php, final class
DealInsightsPeriodFilterFactory.php
DealInsightsPeriodFilterFactoryInterface.php, interface...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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},{"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, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"EventSubscriber, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinition, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Service","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityApiSearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"UserOptionsByGroup.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AbstractStageFilterDefinition.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearchServiceProvider.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactory.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactoryInterface.php, interface","depth":10,"role_description":"text"}]...
|
-9182788673990637262
|
-3734346844552059324
|
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
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
EventSubscriber, folder
FilterDefinition, folder
Service
ActivityApiSearch.php, class
ActivitySearch.php, class
UserOptionsByGroup.php, class
AbstractStageFilterDefinition.php, abstract class
ActivitySearchServiceProvider.php, final class
DealInsightsPeriodFilterFactory.php
DealInsightsPeriodFilterFactoryInterface.php, interface...
|
NULL
|
|
11026
|
218
|
1
|
2026-04-14T09:09:17.295322+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157757295_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests passed: 1","depth":3,"value":"Tests passed: 1","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","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}]...
|
-1117453743363212307
|
-7190066597166601333
|
click
|
hybrid
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
+SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:1686 0Today ~...
|
11023
|
|
11028
|
218
|
2
|
2026-04-14T09:09:19.819064+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157759819_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:1986 0Today ~...
|
NULL
|
512364435268603416
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:1986 0Today ~...
|
NULL
|
|
11029
|
218
|
3
|
2026-04-14T09:09:32.433694+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157772433_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:3286 0Today ~...
|
NULL
|
4246189346641032977
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:3286 0Today ~...
|
11028
|
|
11031
|
218
|
4
|
2026-04-14T09:09:38.109736+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157778109_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:3786 0Today ~...
|
NULL
|
6095902680836267379
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:3786 0Today ~...
|
NULL
|
|
11033
|
218
|
5
|
2026-04-14T09:09:42.889365+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157782889_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:4286 0Today ~...
|
NULL
|
-2825312916008844480
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:4286 0Today ~...
|
11031
|
|
11035
|
218
|
6
|
2026-04-14T09:09:50.316121+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157790316_m1.jpg...
|
PhpStorm
|
faVsco.js – Criteria.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
isFirstRequest
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/1
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
46
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\VO\Repository\OnDemandActivitySearch;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\CreatedPeriodFilter;
class Criteria
{
/**
* Sentinel value to indicate a date parameter was not provided in the request
*/
private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';
/**
* @var string[]
*/
private $playlistIds;
/**
* @var string[]
*/
private array $topicIds = [];
private ?string $compareTopicId = null;
private ?string $partnerId;
/**
* @var string[]
*/
private $activityTypeIds;
/**
* @var string[]
*/
private $stageIds;
/**
* @var string[]
*/
private $currentStageIds;
/**
* @var string[]|null
*/
private ?array $groupIds = null;
/**
* @var string[]|null
*/
private ?array $userIds = null;
/**
* @var string[]|null
*/
private ?array $coachingFeedbackCoachUserId;
/**
* @var string[]|null
*/
private ?array $participantUserIds = null;
/**
* @var string[]|null
*/
private ?array $teamMemberUserIds;
/**
* @var string[]
*/
private $teamIds;
/**
* @var string
*/
private $providerId;
/**
* Channel ID for filtering activities by a single channel.
*/
private ?string $channelId = null;
/**
* Channel IDs for filtering activities by multiple channels.
* When provided, activities matching any of the specified channels will be returned.
*
* @var string[]|null
*/
private ?array $channelIds = null;
/**
* Filter activities based on whether they have a transcription.
* true = only activities with transcription, false = only activities without transcription, null = no filter
*/
private ?bool $hasTranscription = null;
private ?int $minDuration = null;
private ?int $maxDuration = null;
/**
* @var string
*
* @format uuid
*/
private $excludedUserId;
private ?CarbonImmutable $startDate = null;
private ?CarbonImmutable $endDate = null;
/**
* @var Carbon|null
*/
private $scheduledFrom;
private ?Carbon $scheduledTo = null;
/**
* @var Carbon|null
*/
private $updatedFrom;
/**
* @var Carbon|null
*/
private $updatedTo;
private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private ?array $dealStageIds = null;
private ?array $dealPipelineIds = null;
private ?array $dealTypeIds = null;
private ?int $minDealValue = null;
private ?int $maxDealValue = null;
/**
* @var string|null
*/
private $minDealAge;
/**
* @var string|null
*/
private $maxDealAge;
/**
* @var string|null
*/
private $minPatience;
/**
* @var string|null
*/
private $maxPatience;
private ?int $minTalkRatio = null;
private ?int $maxTalkRatio = null;
/**
* @var string|null
*/
private $minMonologue;
/**
* @var string|null
*/
private $maxMonologue;
/**
* @var string|null
*/
private $minCustomerMonologue;
/**
* @var string|null
*/
private $maxCustomerMonologue;
/**
* @var string|null
*/
private $minSpeechRate;
/**
* @var string|null
*/
private $maxSpeechRate;
private ?float $minScore = null;
private ?float $maxScore = null;
/**
* @var int[]|null
*/
private ?array $coachingScores = null;
private ?int $minAutoScore = null;
private ?int $maxAutoScore = null;
/**
* @var int[]|null
*/
private ?array $autoScores = null;
/**
* @var int[]|null
*/
private ?array $aiCallScores = null;
/**
* @var string[]
*/
private ?array $includeStatuses;
/**
* @var bool
*/
private $notLogged;
private bool $hasPendingAiCrmNotes;
private bool $includeHostJoinedMeetings;
private ?int $onlyRecorded;
private ?int $includeInternalConversations = null;
private ?string $searchQuery = null;
/**
* @var string
*/
private $transcriptKeywords;
/**
* @var string
*/
private $transcriptSaidBy;
/**
* @var string
*/
private $transcriptSpeaker;
/**
* @var string[]|null
*/
private $languages;
private ?string $externalId = null;
private ?string $externalIdType = null;
/**
* @var string|null
*/
private $minUserQuestions;
/**
* @var string|null
*/
private $maxUserQuestions;
/**
* @var string|null
*/
private $minEngagingQuestions;
/**
* @var string|null
*/
private $maxEngagingQuestions;
/**
* @var string|null
*/
private $minInsightfulQuestions;
/**
* @var string|null
*/
private $maxInsightfulQuestions;
/**
* @var string|null
*/
private $minCustomerQuestions;
/**
* @var string|null
*/
private $maxCustomerQuestions;
/**
* @var string|null
*/
private $sortDirection;
/**
* @var string|null
*/
private $sortBy;
private int $limit;
/**
* @var int
*/
private $pageNumber;
/**
* @var bool
*/
private $empty;
/**
* @var int
*/
private $sequenceNumber;
/**
* @var string
*/
private $nudgeRunId;
private ?bool $onlyActiveUsers = null;
/**
* @var string
*/
private $activityId;
private ?string $context = null;
private ?string $minCommentCount = null;
private ?string $maxCommentCount = null;
/** @var array<string, string|string[]> */
private array $crmFieldValues = [];
public static function createFromRequest(
array $requestAttributes,
DateTimeZone $timezone,
?string $context = null
): self {
$attributes = Collection::make($requestAttributes);
$instance = new self();
$instance->context = $context;
$instance->partnerId = $attributes->get('partner_id');
$instance->teamIds = $attributes->get('team_id');
$instance->groupIds = $attributes->get('group_id');
$instance->userIds = $attributes->get('user_id');
$instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');
$instance->participantUserIds = $attributes->get('participant_user_id');
$instance->teamMemberUserIds = $attributes->get('team_member_user_id');
$instance->excludedUserId = $attributes->get('excluded_user_id');
$instance->activityTypeIds = $attributes->get('category_id');
$instance->searchQuery = $attributes->get('query');
$instance->transcriptKeywords = $attributes->get('transcript_keywords');
$instance->transcriptSaidBy = $attributes->get('transcript_said_by');
$instance->transcriptSpeaker = $attributes->get('transcript_speaker');
$instance->languages = $attributes->get('languages');
$instance->topicIds = $attributes->get('topic_id', []);
$instance->compareTopicId = $attributes->get('compare_topic_id');
$instance->stageIds = $attributes->get('stage_id');
$instance->currentStageIds = $attributes->get('current_stage_id');
$instance->playlistIds = $attributes->get('playlist_id');
$instance->providerId = $attributes->get('provider_id');
$instance->channelId = $attributes->get('channel_id');
$instance->channelIds = $attributes->get('channel_ids');
$hasTranscription = $attributes->get('has_transcription');
$instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;
$instance->externalId = $attributes->get('external_id');
$instance->externalIdType = $attributes->get('external_id_type');
$instance->notLogged = $attributes->get('not_logged') === '1';
$instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';
$instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';
$instance->onlyRecorded = $attributes->has('only_recorded')
? (int) $attributes->get('only_recorded')
: null;
$instance->includeInternalConversations = $attributes->has('include_internal_conversations')
? (int) $attributes->get('include_internal_conversations')
: null;
$instance->minDuration = $attributes->has('min_duration')
? (int) $attributes->get('min_duration')
: null;
$instance->maxDuration = $attributes->has('max_duration')
? (int) $attributes->get('max_duration')
: null;
$minDealValue = $attributes->get('min_deal_value');
$instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;
$maxDealValue = $attributes->get('max_deal_value');
$instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;
$instance->minDealAge = $attributes->get('min_deal_age');
$instance->maxDealAge = $attributes->get('max_deal_age');
$instance->minCustomerMonologue = $attributes->get('min_customer_monologue');
$instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');
$instance->minMonologue = $attributes->get('min_monologue');
$instance->maxMonologue = $attributes->get('max_monologue');
$instance->minTalkRatio = $attributes->has('min_talk_ratio')
? (int) $attributes->get('min_talk_ratio')
: null;
$instance->maxTalkRatio = $attributes->has('max_talk_ratio')
? (int) $attributes->get('max_talk_ratio')
: null;
$instance->minPatience = $attributes->get('min_patience');
$instance->maxPatience = $attributes->get('max_patience');
$instance->minSpeechRate = $attributes->get('min_speech_rate');
$instance->maxSpeechRate = $attributes->get('max_speech_rate');
$instance->minScore = $attributes->has('min_score')
? (float) $attributes->get('min_score')
: null;
$instance->maxScore = $attributes->has('max_score')
? (float) $attributes->get('max_score')
: null;
$coachingScores = $attributes->get('coaching_score');
if (is_array($coachingScores)) {
$instance->coachingScores = array_map('intval', $coachingScores);
}
$instance->minAutoScore = $attributes->has('min_auto_score')
? (int) $attributes->get('min_auto_score')
: null;
$instance->maxAutoScore = $attributes->has('max_auto_score')
? (int) $attributes->get('max_auto_score')
: null;
$autoScores = $attributes->get('auto_score');
if (is_array($autoScores)) {
$instance->autoScores = array_map('intval', $autoScores);
}
$aiCallScores = $attributes->get('ai_call_score');
if (is_array($aiCallScores)) {
$instance->aiCallScores = array_map('intval', $aiCallScores);
}
$instance->minUserQuestions = $attributes->get('min_user_questions');
$instance->maxUserQuestions = $attributes->get('max_user_questions');
$instance->minEngagingQuestions = $attributes->get('min_engaging_questions');
$instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');
$instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');
$instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');
$instance->minCustomerQuestions = $attributes->get('min_customer_questions');
$instance->maxCustomerQuestions = $attributes->get('max_customer_questions');
$instance->nudgeRunId = $attributes->get('nudge_run_id');
$instance->activityId = $attributes->get('activity_id');
$instance->minCommentCount = $attributes->has('min_comment_count')
? (string) $attributes->get('min_comment_count')
: null;
$instance->maxCommentCount = $attributes->has('max_comment_count')
? (string) $attributes->get('max_comment_count')
: null;
$instance->onlyActiveUsers = $attributes->has('only_active_users')
? $attributes->get('only_active_users') === '1'
: null;
$instance->crmFieldValues = $attributes
->filter(static function ($value, $key): bool {
return str_starts_with($key, FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX);
})
->mapWithKeys(static function ($value, string $key): array {
$key = str_replace_first(FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX, '', $key);
return [
$key => $value,
];
})
->all();
$instance->limit = (int) $attributes->get('limit', 25);
$instance->pageNumber = (int) $attributes->get('page', 1);
$instance->empty = $attributes->isEmpty();
$instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);
$instance->includeStatuses = $attributes->get('status');
$instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);
$instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);
$dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;
$dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;
$instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);
$instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);
if ($attributes->has('deal_pipeline_id')) {
$instance->dealPipelineIds = $attributes->get('deal_pipeline_id');
}
if ($attributes->has('deal_stage_id')) {
$instance->dealStageIds = $attributes->get('deal_stage_id');
}
if ($attributes->has('deal_type_id')) {
$instance->dealTypeIds = $attributes->get('deal_type_id');
}
if ($attributes->has('start_date')) {
$instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);
}
if ($attributes->has('end_date')) {
$instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);
}
if ($attributes->has('scheduled_from')) {
$instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);
}
if ($attributes->has('scheduled_to')) {
$instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);
}
if ($attributes->has('updated_from')) {
$instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);
}
if ($attributes->has('updated_to')) {
$instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);
}
$instance->sortBy = $attributes->get('sort_by');
$instance->sortDirection = $attributes->get('sort_direction');
return $instance;
}
public function setContext(?string $context): self
{
$this->context = $context;
return $this;
}
public function getContext(): ?string
{
return $this->context;
}
public function getMinDealValue(): ?int
{
return $this->minDealValue;
}
public function getMaxDealValue(): ?int
{
return $this->maxDealValue;
}
public function hasMinDealAge(): bool
{
return $this->minDealAge !== null;
}
public function getMinDealAge(): int
{
return (int) $this->minDealAge;
}
public function hasMaxDealAge(): bool
{
return $this->maxDealAge !== null;
}
public function getMaxDealAge(): int
{
return (int) $this->maxDealAge;
}
public function hasTranscriptKeywords(): bool
{
return $this->transcriptKeywords !== null;
}
public function getTranscriptKeywords(): string
{
return $this->transcriptKeywords;
}
public function hasTranscriptSaidBy(): bool
{
return $this->transcriptSaidBy !== null;
}
public function getTranscriptSaidBy(): string
{
return $this->transcriptSaidBy;
}
public function hasTranscriptSpeaker(): bool
{
return $this->transcriptSpeaker !== null;
}
public function getTranscriptSpeaker(): string
{
return $this->transcriptSpeaker;
}
public function getLanguages(): ?array
{
return $this->languages;
}
public function getPartnerId(): ?string
{
return $this->partnerId;
}
public function getMinScore(): ?float
{
return $this->minScore;
}
public function getMaxScore(): ?float
{
return $this->maxScore;
}
/**
* @return int[]|null
*/
public function getCoachingScores(): ?array
{
return $this->coachingScores;
}
public function getMinAutoScore(): ?int
{
return $this->minAutoScore;
}
public function getMaxAutoScore(): ?int
{
return $this->maxAutoScore;
}
/**
* @return int[]|null
*/
public function getAutoScores(): ?array
{
return $this->autoScores;
}
/**
* @return int[]|null
*/
public function getAiCallScores(): ?array
{
return $this->aiCallScores;
}
public function hasMinPatience(): bool
{
return $this->minPatience !== null;
}
public function getMinPatience(): float
{
return (float) $this->minPatience;
}
public function hasMaxPatience(): bool
{
return $this->maxPatience !== null;
}
public function getMaxPatience(): float
{
return (float) $this->maxPatience;
}
public function hasMinSpeechRate(): bool
{
return $this->minSpeechRate !== null;
}
public function getMinSpeechRate(): float
{
return (float) $this->minSpeechRate;
}
public function hasMaxSpeechRate(): bool
{
return $this->maxSpeechRate !== null;
}
public function getMaxSpeechRate(): float
{
return (float) $this->maxSpeechRate;
}
public function getMinTalkRatio(): ?int
{
return $this->minTalkRatio;
}
public function getMaxTalkRatio(): ?int
{
return $this->maxTalkRatio;
}
public function hasMinMonologue(): bool
{
return $this->minMonologue !== null;
}
public function getMinMonologue(): float
{
return (float) $this->minMonologue;
}
public function hasMaxMonologue(): bool
{
return $this->maxMonologue !== null;
}
public function getMaxMonologue(): float
{
return (float) $this->maxMonologue;
}
public function hasMinCustomerMonologue(): bool
{
return $this->minCustomerMonologue !== null;
}
public function getMinCustomerMonologue(): float
{
return (float) $this->minCustomerMonologue;
}
public function hasMaxCustomerMonologue(): bool
{
return $this->maxCustomerMonologue !== null;
}
public function getMaxCustomerMonologue(): float
{
return (float) $this->maxCustomerMonologue;
}
public function getDealCloseDateStart(): ?CarbonImmutable
{
return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;
}
public function getDealCloseDateEnd(): ?CarbonImmutable
{
return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;
}
public function getDealCreatedDateStart(): ?CarbonImmutable
{
return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;
}
public function getDealCreatedDateEnd(): ?CarbonImmutable
{
return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;
}
/**
* Check if deal close date start parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateStart(): bool
{
return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal close date end parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateEnd(): bool
{
return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date start parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateStart(): bool
{
return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date end parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateEnd(): bool
{
return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
public function hasDealStageIds(): bool
{
return $this->dealStageIds !== null;
}
public function getDealStageIds(): array
{
return $this->dealStageIds;
}
public function hasDealPipelineIds(): bool
{
return $this->dealPipelineIds !== null;
}
public function getDealPipelineIds(): array
{
return $this->dealPipelineIds;
}
public function hasDealTypeIds(): bool
{
return $this->dealTypeIds !== null;
}
public function getDealTypeIds(): array
{
return $this->dealTypeIds;
}
public function getExternalId(): ?string
{
return $this->externalId;
}
public function getExternalIdType(): ?string
{
return $this->externalIdType;
}
public function hasNudgeRunId(): bool
{
return $this->nudgeRunId !== null;
}
public function getNudgeRunId(): string
{
return $this->nudgeRunId;
}
public function hasActivityId(): bool
{
return $this->activityId !== null;
}
public function getActivityId(): string
{
return $this->activityId;
}
public function hasPlaylists(): bool
{
return $this->playlistIds !== null;
}
/**
* @return string[]
*/
public function getPlaylistIds(): array
{
return $this->playlistIds;
}
public function hasActivityTypeIds(): bool
{
return $this->activityTypeIds !== null;
}
public function getActivityTypeIds(): array
{
return $this->activityTypeIds;
}
/**
* @return string[]
*/
public function getTopicIds(): array
{
return $this->topicIds;
}
public function getCompareTopicId(): ?string
{
return $this->compareTopicId;
}
public function hasMinDuration(): bool
{
return $this->minDuration !== null;
}
public function getMinDuration(): int
{
return $this->minDuration;
}
public function hasMaxDuration(): bool
{
return $this->maxDuration !== null;
}
public function getMaxDuration(): int
{
return $this->maxDuration;
}
public function hasStageIds(): bool
{
return $this->stageIds !== null;
}
public function getStageIds(): array
{
return $this->stageIds;
}
public function hasCurrentStageIds(): bool
{
return $this->currentStageIds !== null;
}
public function getCurrentStageIds(): array
{
return $this->currentStageIds;
}
public function hasProviderId(): bool
{
return $this->providerId !== null;
}
public function getProviderId(): string
{
return $this->providerId;
}
public function getChannelId(): ?string
{
return $this->channelId;
}
public function getChannelIds(): ?array
{
return $this->channelIds;
}
public function getHasTranscription(): ?bool
{
return $this->hasTranscription;
}
/**
* @return string[]|null
*/
public function getGroupIds(): ?array
{
return $this->groupIds;
}
/** @return array<string, string|string[]> */
public function getCrmFieldValues(): array
{
return $this->crmFieldValues;
}
public function hasCoachingFeedbackCoachUserIds(): bool
{
return $this->coachingFeedbackCoachUserId !== null;
}
/**
* @return string[]
*/
public function getTeamIds(): array
{
return $this->teamIds;
}
public function hasTeamIds(): bool
{
return $this->teamIds !== null;
}
public function hasTeamMemberUserIds(): bool
{
return $this->teamMemberUserIds !== null;
}
/**
* @return string[]
*/
public function getTeamMemberUserIds(): array
{
return $this->teamMemberUserIds;
}
/**
* @return ?string[]
*/
public function getUserIds(): ?array
{
return $this->userIds;
}
public function getCoachingFeedbackCoachUserId(): array
{
return $this->coachingFeedbackCoachUserId;
}
public function getParticipantUserIds(): ?array
{
return $this->participantUserIds;
}
public function hasExcludedUserId(): bool
{
return $this->excludedUserId !== null;
}
public function getExcludedUserId(): string
{
return $this->excludedUserId;
}
public function getStartDate(): ?CarbonImmutable
{
return $this->startDate;
}
public function getEndDate(): ?CarbonImmutable
{
return $this->endDate;
}
public function getScheduledFrom(): ?Carbon
{
return $this->scheduledFrom;
}
public function getScheduledTo(): ?Carbon
{
return $this->scheduledTo;
}
public function hasUpdatedFrom(): bool
{
return $this->updatedFrom !== null;
}
public function getUpdatedFrom(): Carbon
{
return $this->updatedFrom;
}
public function hasUpdatedTo(): bool
{
return $this->updatedTo !== null;
}
public function getUpdatedTo(): Carbon
{
return $this->updatedTo;
}
public function hasActivityStatusValues(): bool
{
return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;
}
/**
* @return string[]
*/
public function getActivityStatusValues(): array
{
return $this->includeStatuses;
}
public function onlyNotLoggedActivities(): bool
{
return $this->notLogged === true;
}
public function hasPendingAiCrmNotes(): bool
{
return $this->hasPendingAiCrmNotes === true;
}
public function hasIncludeHostJoinedMeetings(): bool
{
return $this->includeHostJoinedMeetings === true;
}
public function hasOnlyRecordedActivities(): bool
{
return $this->onlyRecorded !== null;
}
public function getOnlyRecordedActivities(): int
{
return $this->onlyRecorded;
}
public function getIncludeInternalConversations(): ?int
{
return $this->includeInternalConversations;
}
public function hasSearchQuery(): bool
{
return $this->searchQuery !== null;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
public function getSearchQuery(): ?string
{
return $this->searchQuery;
}
public function hasMinUserQuestions(): bool
{
return $this->minUserQuestions !== null;
}
public function getMinUserQuestions(): int
{
return (int) $this->minUserQuestions;
}
public function hasMaxUserQuestions(): bool
{
return $this->maxUserQuestions !== null;
}
public function getMaxUserQuestions(): int
{
return (int) $this->maxUserQuestions;
}
public function hasMinEngagingQuestions(): bool
{
return $this->minEngagingQuestions !== null;
}
public function getMinEngagingQuestions(): int
{
return (int) $this->minEngagingQuestions;
}
public function hasMaxEngagingQuestions(): bool
{
return $this->maxEngagingQuestions !== null;
}
public function getMaxEngagingQuestions(): int
{
return (int) $this->maxEngagingQuestions;
}
public function hasMinInsightfulQuestions(): bool
{
return $this->minInsightfulQuestions !== null;
}
public function getMinInsightfulQuestions(): int
{
return (int) $this->minInsightfulQuestions;
}
public function hasMaxInsightfulQuestions(): bool
{
return $this->maxInsightfulQuestions !== null;
}
public function getMaxInsightfulQuestions(): int
{
return (int) $this->maxInsightfulQuestions;
}
public function hasMinCustomerQuestions(): bool
{
return $this->minCustomerQuestions !== null;
}
public function getMinCustomerQuestions(): int
{
return (int) $this->minCustomerQuestions;
}
public function hasMaxCustomerQuestions(): bool
{
return $this->maxCustomerQuestions !== null;
}
public function getMaxCustomerQuestions(): int
{
return (int) $this->maxCustomerQuestions;
}
public function hasMinCommentCount(): bool
{
return $this->minCommentCount !== null;
}
public function getMinCommentCount(): string
{
return $this->minCommentCount;
}
public function hasMaxCommentCount(): bool
{
return $this->maxCommentCount !== null;
}
public function getMaxCommentCount(): string
{
return $this->maxCommentCount;
}
public function hasSortDirection(): bool
{
return $this->sortDirection !== null;
}
public function getSortDirection(): string
{
return $this->sortDirection;
}
public function hasSortBy(): bool
{
return $this->sortBy !== null;
}
public function getSortBy(): string
{
return $this->sortBy;
}
public function getLimit(): int
{
return $this->limit;
}
public function isEmpty(): bool
{
return $this->empty;
}
public function isFirstRequest(): bool
{
return $this->empty || $this->sequenceNumber === 0;
}
public function getOnlyActiveUsers(): bool
{
return $this->onlyActiveUsers === true;
}
/**
* Parse a Deal Insights date from request attributes, handling null/empty values gracefully.
* This method is specifically for Deal Insights date fields to support the "All time" filter.
*
* @param Collection $attributes
* @param string $key
* @param DateTimeZone $timezone
*
* @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is
* missing, null if explicitly null, CarbonImmutable if valid date
*/
private static function parseDealInsightsDate(
Collection $attributes,
string $key,
DateTimeZone $timezone
): CarbonImmutable|string|null {
if (! $attributes->has($key)) {
return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)
}
$value = $attributes->get($key);
if (empty($value) || $value === 'null') {
return null; // Parameter is explicitly null (should use "All time")
}
return CarbonImmutable::parse($value, $timezone);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
EventSubscriber, folder
FilterDefinition, folder
Service
ActivityApiSearch.php, class
ActivitySearch.php, class
UserOptionsByGroup.php, class
AbstractStageFilterDefinition.php, abstract class
ActivitySearchServiceProvider.php, final class
DealInsightsPeriodFilterFactory.php
DealInsightsPeriodFilterFactoryInterface.php, interface
FilterDefinition.php, abstract class
FilterDefinitionCollection.php, class
FilterDefinitionQuery.php, class
FilterDefinitionQueryCollection.php, class
FilteredValueContainerInterface.php, interface
IntMinMaxRange.php, class
AiActivityType
AiAutomation
AiCallScoring
AskAnything
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"isFirstRequest","depth":4,"value":"isFirstRequest","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1/1","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"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":"46","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","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\\VO\\Repository\\OnDemandActivitySearch;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse DateTimeZone;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\CreatedPeriodFilter;\n\nclass Criteria\n{\n /**\n * Sentinel value to indicate a date parameter was not provided in the request\n */\n private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';\n\n /**\n * @var string[]\n */\n private $playlistIds;\n\n /**\n * @var string[]\n */\n private array $topicIds = [];\n\n private ?string $compareTopicId = null;\n\n private ?string $partnerId;\n\n /**\n * @var string[]\n */\n private $activityTypeIds;\n\n /**\n * @var string[]\n */\n private $stageIds;\n\n /**\n * @var string[]\n */\n private $currentStageIds;\n\n /**\n * @var string[]|null\n */\n private ?array $groupIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $userIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $coachingFeedbackCoachUserId;\n\n /**\n * @var string[]|null\n */\n private ?array $participantUserIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $teamMemberUserIds;\n\n /**\n * @var string[]\n */\n private $teamIds;\n\n /**\n * @var string\n */\n private $providerId;\n\n /**\n * Channel ID for filtering activities by a single channel.\n */\n private ?string $channelId = null;\n\n /**\n * Channel IDs for filtering activities by multiple channels.\n * When provided, activities matching any of the specified channels will be returned.\n *\n * @var string[]|null\n */\n private ?array $channelIds = null;\n\n /**\n * Filter activities based on whether they have a transcription.\n * true = only activities with transcription, false = only activities without transcription, null = no filter\n */\n private ?bool $hasTranscription = null;\n\n private ?int $minDuration = null;\n private ?int $maxDuration = null;\n\n /**\n * @var string\n *\n * @format uuid\n */\n private $excludedUserId;\n\n private ?CarbonImmutable $startDate = null;\n private ?CarbonImmutable $endDate = null;\n\n /**\n * @var Carbon|null\n */\n private $scheduledFrom;\n\n private ?Carbon $scheduledTo = null;\n\n /**\n * @var Carbon|null\n */\n private $updatedFrom;\n\n /**\n * @var Carbon|null\n */\n private $updatedTo;\n\n private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private ?array $dealStageIds = null;\n private ?array $dealPipelineIds = null;\n private ?array $dealTypeIds = null;\n\n private ?int $minDealValue = null;\n private ?int $maxDealValue = null;\n\n /**\n * @var string|null\n */\n private $minDealAge;\n\n /**\n * @var string|null\n */\n private $maxDealAge;\n\n /**\n * @var string|null\n */\n private $minPatience;\n\n /**\n * @var string|null\n */\n private $maxPatience;\n\n private ?int $minTalkRatio = null;\n private ?int $maxTalkRatio = null;\n\n /**\n * @var string|null\n */\n private $minMonologue;\n\n /**\n * @var string|null\n */\n private $maxMonologue;\n\n /**\n * @var string|null\n */\n private $minCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $maxCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $minSpeechRate;\n\n /**\n * @var string|null\n */\n private $maxSpeechRate;\n\n private ?float $minScore = null;\n private ?float $maxScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $coachingScores = null;\n\n private ?int $minAutoScore = null;\n private ?int $maxAutoScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $autoScores = null;\n\n /**\n * @var int[]|null\n */\n private ?array $aiCallScores = null;\n\n /**\n * @var string[]\n */\n private ?array $includeStatuses;\n\n /**\n * @var bool\n */\n private $notLogged;\n\n private bool $hasPendingAiCrmNotes;\n\n private bool $includeHostJoinedMeetings;\n\n private ?int $onlyRecorded;\n\n private ?int $includeInternalConversations = null;\n\n private ?string $searchQuery = null;\n\n /**\n * @var string\n */\n private $transcriptKeywords;\n\n /**\n * @var string\n */\n private $transcriptSaidBy;\n\n /**\n * @var string\n */\n private $transcriptSpeaker;\n\n /**\n * @var string[]|null\n */\n private $languages;\n\n private ?string $externalId = null;\n private ?string $externalIdType = null;\n\n /**\n * @var string|null\n */\n private $minUserQuestions;\n\n /**\n * @var string|null\n */\n private $maxUserQuestions;\n\n /**\n * @var string|null\n */\n private $minEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $maxEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $minInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $maxInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $minCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $maxCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $sortDirection;\n\n /**\n * @var string|null\n */\n private $sortBy;\n\n private int $limit;\n\n /**\n * @var int\n */\n private $pageNumber;\n\n /**\n * @var bool\n */\n private $empty;\n\n /**\n * @var int\n */\n private $sequenceNumber;\n\n /**\n * @var string\n */\n private $nudgeRunId;\n\n private ?bool $onlyActiveUsers = null;\n\n /**\n * @var string\n */\n private $activityId;\n\n private ?string $context = null;\n\n private ?string $minCommentCount = null;\n private ?string $maxCommentCount = null;\n /** @var array<string, string|string[]> */\n private array $crmFieldValues = [];\n\n public static function createFromRequest(\n array $requestAttributes,\n DateTimeZone $timezone,\n ?string $context = null\n ): self {\n $attributes = Collection::make($requestAttributes);\n\n $instance = new self();\n $instance->context = $context;\n\n $instance->partnerId = $attributes->get('partner_id');\n $instance->teamIds = $attributes->get('team_id');\n $instance->groupIds = $attributes->get('group_id');\n $instance->userIds = $attributes->get('user_id');\n $instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');\n $instance->participantUserIds = $attributes->get('participant_user_id');\n $instance->teamMemberUserIds = $attributes->get('team_member_user_id');\n $instance->excludedUserId = $attributes->get('excluded_user_id');\n $instance->activityTypeIds = $attributes->get('category_id');\n $instance->searchQuery = $attributes->get('query');\n $instance->transcriptKeywords = $attributes->get('transcript_keywords');\n $instance->transcriptSaidBy = $attributes->get('transcript_said_by');\n $instance->transcriptSpeaker = $attributes->get('transcript_speaker');\n $instance->languages = $attributes->get('languages');\n $instance->topicIds = $attributes->get('topic_id', []);\n $instance->compareTopicId = $attributes->get('compare_topic_id');\n $instance->stageIds = $attributes->get('stage_id');\n $instance->currentStageIds = $attributes->get('current_stage_id');\n $instance->playlistIds = $attributes->get('playlist_id');\n $instance->providerId = $attributes->get('provider_id');\n $instance->channelId = $attributes->get('channel_id');\n $instance->channelIds = $attributes->get('channel_ids');\n $hasTranscription = $attributes->get('has_transcription');\n $instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;\n $instance->externalId = $attributes->get('external_id');\n $instance->externalIdType = $attributes->get('external_id_type');\n $instance->notLogged = $attributes->get('not_logged') === '1';\n $instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';\n $instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';\n $instance->onlyRecorded = $attributes->has('only_recorded')\n ? (int) $attributes->get('only_recorded')\n : null;\n $instance->includeInternalConversations = $attributes->has('include_internal_conversations')\n ? (int) $attributes->get('include_internal_conversations')\n : null;\n $instance->minDuration = $attributes->has('min_duration')\n ? (int) $attributes->get('min_duration')\n : null;\n $instance->maxDuration = $attributes->has('max_duration')\n ? (int) $attributes->get('max_duration')\n : null;\n $minDealValue = $attributes->get('min_deal_value');\n $instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;\n $maxDealValue = $attributes->get('max_deal_value');\n $instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;\n $instance->minDealAge = $attributes->get('min_deal_age');\n $instance->maxDealAge = $attributes->get('max_deal_age');\n $instance->minCustomerMonologue = $attributes->get('min_customer_monologue');\n $instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');\n $instance->minMonologue = $attributes->get('min_monologue');\n $instance->maxMonologue = $attributes->get('max_monologue');\n $instance->minTalkRatio = $attributes->has('min_talk_ratio')\n ? (int) $attributes->get('min_talk_ratio')\n : null;\n $instance->maxTalkRatio = $attributes->has('max_talk_ratio')\n ? (int) $attributes->get('max_talk_ratio')\n : null;\n $instance->minPatience = $attributes->get('min_patience');\n $instance->maxPatience = $attributes->get('max_patience');\n $instance->minSpeechRate = $attributes->get('min_speech_rate');\n $instance->maxSpeechRate = $attributes->get('max_speech_rate');\n $instance->minScore = $attributes->has('min_score')\n ? (float) $attributes->get('min_score')\n : null;\n $instance->maxScore = $attributes->has('max_score')\n ? (float) $attributes->get('max_score')\n : null;\n\n $coachingScores = $attributes->get('coaching_score');\n if (is_array($coachingScores)) {\n $instance->coachingScores = array_map('intval', $coachingScores);\n }\n\n $instance->minAutoScore = $attributes->has('min_auto_score')\n ? (int) $attributes->get('min_auto_score')\n : null;\n $instance->maxAutoScore = $attributes->has('max_auto_score')\n ? (int) $attributes->get('max_auto_score')\n : null;\n\n $autoScores = $attributes->get('auto_score');\n if (is_array($autoScores)) {\n $instance->autoScores = array_map('intval', $autoScores);\n }\n\n $aiCallScores = $attributes->get('ai_call_score');\n if (is_array($aiCallScores)) {\n $instance->aiCallScores = array_map('intval', $aiCallScores);\n }\n $instance->minUserQuestions = $attributes->get('min_user_questions');\n $instance->maxUserQuestions = $attributes->get('max_user_questions');\n $instance->minEngagingQuestions = $attributes->get('min_engaging_questions');\n $instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');\n $instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');\n $instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');\n $instance->minCustomerQuestions = $attributes->get('min_customer_questions');\n $instance->maxCustomerQuestions = $attributes->get('max_customer_questions');\n $instance->nudgeRunId = $attributes->get('nudge_run_id');\n $instance->activityId = $attributes->get('activity_id');\n $instance->minCommentCount = $attributes->has('min_comment_count')\n ? (string) $attributes->get('min_comment_count')\n : null;\n $instance->maxCommentCount = $attributes->has('max_comment_count')\n ? (string) $attributes->get('max_comment_count')\n : null;\n $instance->onlyActiveUsers = $attributes->has('only_active_users')\n ? $attributes->get('only_active_users') === '1'\n : null;\n\n $instance->crmFieldValues = $attributes\n ->filter(static function ($value, $key): bool {\n return str_starts_with($key, FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX);\n })\n ->mapWithKeys(static function ($value, string $key): array {\n $key = str_replace_first(FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX, '', $key);\n\n return [\n $key => $value,\n ];\n })\n ->all();\n\n $instance->limit = (int) $attributes->get('limit', 25);\n $instance->pageNumber = (int) $attributes->get('page', 1);\n $instance->empty = $attributes->isEmpty();\n $instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);\n\n $instance->includeStatuses = $attributes->get('status');\n\n $instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);\n $instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);\n\n $dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;\n $dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;\n\n $instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);\n $instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);\n\n if ($attributes->has('deal_pipeline_id')) {\n $instance->dealPipelineIds = $attributes->get('deal_pipeline_id');\n }\n\n if ($attributes->has('deal_stage_id')) {\n $instance->dealStageIds = $attributes->get('deal_stage_id');\n }\n\n if ($attributes->has('deal_type_id')) {\n $instance->dealTypeIds = $attributes->get('deal_type_id');\n }\n\n if ($attributes->has('start_date')) {\n $instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);\n }\n\n if ($attributes->has('end_date')) {\n $instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);\n }\n\n if ($attributes->has('scheduled_from')) {\n $instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);\n }\n\n if ($attributes->has('scheduled_to')) {\n $instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);\n }\n\n if ($attributes->has('updated_from')) {\n $instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);\n }\n\n if ($attributes->has('updated_to')) {\n $instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);\n }\n\n $instance->sortBy = $attributes->get('sort_by');\n $instance->sortDirection = $attributes->get('sort_direction');\n\n return $instance;\n }\n\n public function setContext(?string $context): self\n {\n $this->context = $context;\n\n return $this;\n }\n\n public function getContext(): ?string\n {\n return $this->context;\n }\n\n public function getMinDealValue(): ?int\n {\n return $this->minDealValue;\n }\n\n public function getMaxDealValue(): ?int\n {\n return $this->maxDealValue;\n }\n\n public function hasMinDealAge(): bool\n {\n return $this->minDealAge !== null;\n }\n\n public function getMinDealAge(): int\n {\n return (int) $this->minDealAge;\n }\n\n public function hasMaxDealAge(): bool\n {\n return $this->maxDealAge !== null;\n }\n\n public function getMaxDealAge(): int\n {\n return (int) $this->maxDealAge;\n }\n\n public function hasTranscriptKeywords(): bool\n {\n return $this->transcriptKeywords !== null;\n }\n\n public function getTranscriptKeywords(): string\n {\n return $this->transcriptKeywords;\n }\n\n public function hasTranscriptSaidBy(): bool\n {\n return $this->transcriptSaidBy !== null;\n }\n\n public function getTranscriptSaidBy(): string\n {\n return $this->transcriptSaidBy;\n }\n\n public function hasTranscriptSpeaker(): bool\n {\n return $this->transcriptSpeaker !== null;\n }\n\n public function getTranscriptSpeaker(): string\n {\n return $this->transcriptSpeaker;\n }\n\n public function getLanguages(): ?array\n {\n return $this->languages;\n }\n\n public function getPartnerId(): ?string\n {\n return $this->partnerId;\n }\n\n public function getMinScore(): ?float\n {\n return $this->minScore;\n }\n\n public function getMaxScore(): ?float\n {\n return $this->maxScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getCoachingScores(): ?array\n {\n return $this->coachingScores;\n }\n\n public function getMinAutoScore(): ?int\n {\n return $this->minAutoScore;\n }\n\n public function getMaxAutoScore(): ?int\n {\n return $this->maxAutoScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getAutoScores(): ?array\n {\n return $this->autoScores;\n }\n\n /**\n * @return int[]|null\n */\n public function getAiCallScores(): ?array\n {\n return $this->aiCallScores;\n }\n\n public function hasMinPatience(): bool\n {\n return $this->minPatience !== null;\n }\n\n public function getMinPatience(): float\n {\n return (float) $this->minPatience;\n }\n\n public function hasMaxPatience(): bool\n {\n return $this->maxPatience !== null;\n }\n\n public function getMaxPatience(): float\n {\n return (float) $this->maxPatience;\n }\n\n public function hasMinSpeechRate(): bool\n {\n return $this->minSpeechRate !== null;\n }\n\n public function getMinSpeechRate(): float\n {\n return (float) $this->minSpeechRate;\n }\n\n public function hasMaxSpeechRate(): bool\n {\n return $this->maxSpeechRate !== null;\n }\n\n public function getMaxSpeechRate(): float\n {\n return (float) $this->maxSpeechRate;\n }\n\n public function getMinTalkRatio(): ?int\n {\n return $this->minTalkRatio;\n }\n\n public function getMaxTalkRatio(): ?int\n {\n return $this->maxTalkRatio;\n }\n\n public function hasMinMonologue(): bool\n {\n return $this->minMonologue !== null;\n }\n\n public function getMinMonologue(): float\n {\n return (float) $this->minMonologue;\n }\n\n public function hasMaxMonologue(): bool\n {\n return $this->maxMonologue !== null;\n }\n\n public function getMaxMonologue(): float\n {\n return (float) $this->maxMonologue;\n }\n\n public function hasMinCustomerMonologue(): bool\n {\n return $this->minCustomerMonologue !== null;\n }\n\n public function getMinCustomerMonologue(): float\n {\n return (float) $this->minCustomerMonologue;\n }\n\n public function hasMaxCustomerMonologue(): bool\n {\n return $this->maxCustomerMonologue !== null;\n }\n\n public function getMaxCustomerMonologue(): float\n {\n return (float) $this->maxCustomerMonologue;\n }\n\n public function getDealCloseDateStart(): ?CarbonImmutable\n {\n return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;\n }\n\n public function getDealCloseDateEnd(): ?CarbonImmutable\n {\n return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;\n }\n\n public function getDealCreatedDateStart(): ?CarbonImmutable\n {\n return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;\n }\n\n public function getDealCreatedDateEnd(): ?CarbonImmutable\n {\n return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;\n }\n\n /**\n * Check if deal close date start parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateStart(): bool\n {\n return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal close date end parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateEnd(): bool\n {\n return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date start parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateStart(): bool\n {\n return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date end parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateEnd(): bool\n {\n return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n public function hasDealStageIds(): bool\n {\n return $this->dealStageIds !== null;\n }\n\n public function getDealStageIds(): array\n {\n return $this->dealStageIds;\n }\n\n public function hasDealPipelineIds(): bool\n {\n return $this->dealPipelineIds !== null;\n }\n\n public function getDealPipelineIds(): array\n {\n return $this->dealPipelineIds;\n }\n\n public function hasDealTypeIds(): bool\n {\n return $this->dealTypeIds !== null;\n }\n\n public function getDealTypeIds(): array\n {\n return $this->dealTypeIds;\n }\n\n public function getExternalId(): ?string\n {\n return $this->externalId;\n }\n\n public function getExternalIdType(): ?string\n {\n return $this->externalIdType;\n }\n\n public function hasNudgeRunId(): bool\n {\n return $this->nudgeRunId !== null;\n }\n\n public function getNudgeRunId(): string\n {\n return $this->nudgeRunId;\n }\n\n public function hasActivityId(): bool\n {\n return $this->activityId !== null;\n }\n\n public function getActivityId(): string\n {\n return $this->activityId;\n }\n\n public function hasPlaylists(): bool\n {\n return $this->playlistIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getPlaylistIds(): array\n {\n return $this->playlistIds;\n }\n\n public function hasActivityTypeIds(): bool\n {\n return $this->activityTypeIds !== null;\n }\n\n public function getActivityTypeIds(): array\n {\n return $this->activityTypeIds;\n }\n\n /**\n * @return string[]\n */\n public function getTopicIds(): array\n {\n return $this->topicIds;\n }\n\n public function getCompareTopicId(): ?string\n {\n return $this->compareTopicId;\n }\n\n public function hasMinDuration(): bool\n {\n return $this->minDuration !== null;\n }\n\n public function getMinDuration(): int\n {\n return $this->minDuration;\n }\n\n public function hasMaxDuration(): bool\n {\n return $this->maxDuration !== null;\n }\n\n public function getMaxDuration(): int\n {\n return $this->maxDuration;\n }\n\n public function hasStageIds(): bool\n {\n return $this->stageIds !== null;\n }\n\n public function getStageIds(): array\n {\n return $this->stageIds;\n }\n\n public function hasCurrentStageIds(): bool\n {\n return $this->currentStageIds !== null;\n }\n\n public function getCurrentStageIds(): array\n {\n return $this->currentStageIds;\n }\n\n public function hasProviderId(): bool\n {\n return $this->providerId !== null;\n }\n\n public function getProviderId(): string\n {\n return $this->providerId;\n }\n\n public function getChannelId(): ?string\n {\n return $this->channelId;\n }\n\n public function getChannelIds(): ?array\n {\n return $this->channelIds;\n }\n\n public function getHasTranscription(): ?bool\n {\n return $this->hasTranscription;\n }\n\n /**\n * @return string[]|null\n */\n public function getGroupIds(): ?array\n {\n return $this->groupIds;\n }\n\n /** @return array<string, string|string[]> */\n public function getCrmFieldValues(): array\n {\n return $this->crmFieldValues;\n }\n\n public function hasCoachingFeedbackCoachUserIds(): bool\n {\n return $this->coachingFeedbackCoachUserId !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamIds(): array\n {\n return $this->teamIds;\n }\n\n public function hasTeamIds(): bool\n {\n return $this->teamIds !== null;\n }\n\n public function hasTeamMemberUserIds(): bool\n {\n return $this->teamMemberUserIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamMemberUserIds(): array\n {\n return $this->teamMemberUserIds;\n }\n\n /**\n * @return ?string[]\n */\n public function getUserIds(): ?array\n {\n return $this->userIds;\n }\n\n public function getCoachingFeedbackCoachUserId(): array\n {\n return $this->coachingFeedbackCoachUserId;\n }\n\n public function getParticipantUserIds(): ?array\n {\n return $this->participantUserIds;\n }\n\n\n public function hasExcludedUserId(): bool\n {\n return $this->excludedUserId !== null;\n }\n\n public function getExcludedUserId(): string\n {\n return $this->excludedUserId;\n }\n\n public function getStartDate(): ?CarbonImmutable\n {\n return $this->startDate;\n }\n\n public function getEndDate(): ?CarbonImmutable\n {\n return $this->endDate;\n }\n\n public function getScheduledFrom(): ?Carbon\n {\n return $this->scheduledFrom;\n }\n\n public function getScheduledTo(): ?Carbon\n {\n return $this->scheduledTo;\n }\n\n public function hasUpdatedFrom(): bool\n {\n return $this->updatedFrom !== null;\n }\n\n public function getUpdatedFrom(): Carbon\n {\n return $this->updatedFrom;\n }\n\n public function hasUpdatedTo(): bool\n {\n return $this->updatedTo !== null;\n }\n\n public function getUpdatedTo(): Carbon\n {\n return $this->updatedTo;\n }\n\n public function hasActivityStatusValues(): bool\n {\n return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;\n }\n\n /**\n * @return string[]\n */\n public function getActivityStatusValues(): array\n {\n return $this->includeStatuses;\n }\n\n public function onlyNotLoggedActivities(): bool\n {\n return $this->notLogged === true;\n }\n\n public function hasPendingAiCrmNotes(): bool\n {\n return $this->hasPendingAiCrmNotes === true;\n }\n\n public function hasIncludeHostJoinedMeetings(): bool\n {\n return $this->includeHostJoinedMeetings === true;\n }\n\n public function hasOnlyRecordedActivities(): bool\n {\n return $this->onlyRecorded !== null;\n }\n\n public function getOnlyRecordedActivities(): int\n {\n return $this->onlyRecorded;\n }\n\n public function getIncludeInternalConversations(): ?int\n {\n return $this->includeInternalConversations;\n }\n\n public function hasSearchQuery(): bool\n {\n return $this->searchQuery !== null;\n }\n\n public function getPageNumber(): int\n {\n return $this->pageNumber;\n }\n\n public function getSearchQuery(): ?string\n {\n return $this->searchQuery;\n }\n\n public function hasMinUserQuestions(): bool\n {\n return $this->minUserQuestions !== null;\n }\n\n public function getMinUserQuestions(): int\n {\n return (int) $this->minUserQuestions;\n }\n\n public function hasMaxUserQuestions(): bool\n {\n return $this->maxUserQuestions !== null;\n }\n\n public function getMaxUserQuestions(): int\n {\n return (int) $this->maxUserQuestions;\n }\n\n public function hasMinEngagingQuestions(): bool\n {\n return $this->minEngagingQuestions !== null;\n }\n\n public function getMinEngagingQuestions(): int\n {\n return (int) $this->minEngagingQuestions;\n }\n\n public function hasMaxEngagingQuestions(): bool\n {\n return $this->maxEngagingQuestions !== null;\n }\n\n public function getMaxEngagingQuestions(): int\n {\n return (int) $this->maxEngagingQuestions;\n }\n\n public function hasMinInsightfulQuestions(): bool\n {\n return $this->minInsightfulQuestions !== null;\n }\n\n public function getMinInsightfulQuestions(): int\n {\n return (int) $this->minInsightfulQuestions;\n }\n\n public function hasMaxInsightfulQuestions(): bool\n {\n return $this->maxInsightfulQuestions !== null;\n }\n\n public function getMaxInsightfulQuestions(): int\n {\n return (int) $this->maxInsightfulQuestions;\n }\n\n public function hasMinCustomerQuestions(): bool\n {\n return $this->minCustomerQuestions !== null;\n }\n\n public function getMinCustomerQuestions(): int\n {\n return (int) $this->minCustomerQuestions;\n }\n\n public function hasMaxCustomerQuestions(): bool\n {\n return $this->maxCustomerQuestions !== null;\n }\n\n public function getMaxCustomerQuestions(): int\n {\n return (int) $this->maxCustomerQuestions;\n }\n\n public function hasMinCommentCount(): bool\n {\n return $this->minCommentCount !== null;\n }\n\n public function getMinCommentCount(): string\n {\n return $this->minCommentCount;\n }\n\n public function hasMaxCommentCount(): bool\n {\n return $this->maxCommentCount !== null;\n }\n\n public function getMaxCommentCount(): string\n {\n return $this->maxCommentCount;\n }\n\n public function hasSortDirection(): bool\n {\n return $this->sortDirection !== null;\n }\n\n public function getSortDirection(): string\n {\n return $this->sortDirection;\n }\n\n public function hasSortBy(): bool\n {\n return $this->sortBy !== null;\n }\n\n public function getSortBy(): string\n {\n return $this->sortBy;\n }\n\n public function getLimit(): int\n {\n return $this->limit;\n }\n\n public function isEmpty(): bool\n {\n return $this->empty;\n }\n\n public function isFirstRequest(): bool\n {\n return $this->empty || $this->sequenceNumber === 0;\n }\n\n public function getOnlyActiveUsers(): bool\n {\n return $this->onlyActiveUsers === true;\n }\n\n /**\n * Parse a Deal Insights date from request attributes, handling null/empty values gracefully.\n * This method is specifically for Deal Insights date fields to support the \"All time\" filter.\n *\n * @param Collection $attributes\n * @param string $key\n * @param DateTimeZone $timezone\n *\n * @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is\n * missing, null if explicitly null, CarbonImmutable if valid date\n */\n private static function parseDealInsightsDate(\n Collection $attributes,\n string $key,\n DateTimeZone $timezone\n ): CarbonImmutable|string|null {\n if (! $attributes->has($key)) {\n return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)\n }\n\n $value = $attributes->get($key);\n\n if (empty($value) || $value === 'null') {\n return null; // Parameter is explicitly null (should use \"All time\")\n }\n\n return CarbonImmutable::parse($value, $timezone);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\VO\\Repository\\OnDemandActivitySearch;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse DateTimeZone;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\CreatedPeriodFilter;\n\nclass Criteria\n{\n /**\n * Sentinel value to indicate a date parameter was not provided in the request\n */\n private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';\n\n /**\n * @var string[]\n */\n private $playlistIds;\n\n /**\n * @var string[]\n */\n private array $topicIds = [];\n\n private ?string $compareTopicId = null;\n\n private ?string $partnerId;\n\n /**\n * @var string[]\n */\n private $activityTypeIds;\n\n /**\n * @var string[]\n */\n private $stageIds;\n\n /**\n * @var string[]\n */\n private $currentStageIds;\n\n /**\n * @var string[]|null\n */\n private ?array $groupIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $userIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $coachingFeedbackCoachUserId;\n\n /**\n * @var string[]|null\n */\n private ?array $participantUserIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $teamMemberUserIds;\n\n /**\n * @var string[]\n */\n private $teamIds;\n\n /**\n * @var string\n */\n private $providerId;\n\n /**\n * Channel ID for filtering activities by a single channel.\n */\n private ?string $channelId = null;\n\n /**\n * Channel IDs for filtering activities by multiple channels.\n * When provided, activities matching any of the specified channels will be returned.\n *\n * @var string[]|null\n */\n private ?array $channelIds = null;\n\n /**\n * Filter activities based on whether they have a transcription.\n * true = only activities with transcription, false = only activities without transcription, null = no filter\n */\n private ?bool $hasTranscription = null;\n\n private ?int $minDuration = null;\n private ?int $maxDuration = null;\n\n /**\n * @var string\n *\n * @format uuid\n */\n private $excludedUserId;\n\n private ?CarbonImmutable $startDate = null;\n private ?CarbonImmutable $endDate = null;\n\n /**\n * @var Carbon|null\n */\n private $scheduledFrom;\n\n private ?Carbon $scheduledTo = null;\n\n /**\n * @var Carbon|null\n */\n private $updatedFrom;\n\n /**\n * @var Carbon|null\n */\n private $updatedTo;\n\n private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private ?array $dealStageIds = null;\n private ?array $dealPipelineIds = null;\n private ?array $dealTypeIds = null;\n\n private ?int $minDealValue = null;\n private ?int $maxDealValue = null;\n\n /**\n * @var string|null\n */\n private $minDealAge;\n\n /**\n * @var string|null\n */\n private $maxDealAge;\n\n /**\n * @var string|null\n */\n private $minPatience;\n\n /**\n * @var string|null\n */\n private $maxPatience;\n\n private ?int $minTalkRatio = null;\n private ?int $maxTalkRatio = null;\n\n /**\n * @var string|null\n */\n private $minMonologue;\n\n /**\n * @var string|null\n */\n private $maxMonologue;\n\n /**\n * @var string|null\n */\n private $minCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $maxCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $minSpeechRate;\n\n /**\n * @var string|null\n */\n private $maxSpeechRate;\n\n private ?float $minScore = null;\n private ?float $maxScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $coachingScores = null;\n\n private ?int $minAutoScore = null;\n private ?int $maxAutoScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $autoScores = null;\n\n /**\n * @var int[]|null\n */\n private ?array $aiCallScores = null;\n\n /**\n * @var string[]\n */\n private ?array $includeStatuses;\n\n /**\n * @var bool\n */\n private $notLogged;\n\n private bool $hasPendingAiCrmNotes;\n\n private bool $includeHostJoinedMeetings;\n\n private ?int $onlyRecorded;\n\n private ?int $includeInternalConversations = null;\n\n private ?string $searchQuery = null;\n\n /**\n * @var string\n */\n private $transcriptKeywords;\n\n /**\n * @var string\n */\n private $transcriptSaidBy;\n\n /**\n * @var string\n */\n private $transcriptSpeaker;\n\n /**\n * @var string[]|null\n */\n private $languages;\n\n private ?string $externalId = null;\n private ?string $externalIdType = null;\n\n /**\n * @var string|null\n */\n private $minUserQuestions;\n\n /**\n * @var string|null\n */\n private $maxUserQuestions;\n\n /**\n * @var string|null\n */\n private $minEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $maxEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $minInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $maxInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $minCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $maxCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $sortDirection;\n\n /**\n * @var string|null\n */\n private $sortBy;\n\n private int $limit;\n\n /**\n * @var int\n */\n private $pageNumber;\n\n /**\n * @var bool\n */\n private $empty;\n\n /**\n * @var int\n */\n private $sequenceNumber;\n\n /**\n * @var string\n */\n private $nudgeRunId;\n\n private ?bool $onlyActiveUsers = null;\n\n /**\n * @var string\n */\n private $activityId;\n\n private ?string $context = null;\n\n private ?string $minCommentCount = null;\n private ?string $maxCommentCount = null;\n /** @var array<string, string|string[]> */\n private array $crmFieldValues = [];\n\n public static function createFromRequest(\n array $requestAttributes,\n DateTimeZone $timezone,\n ?string $context = null\n ): self {\n $attributes = Collection::make($requestAttributes);\n\n $instance = new self();\n $instance->context = $context;\n\n $instance->partnerId = $attributes->get('partner_id');\n $instance->teamIds = $attributes->get('team_id');\n $instance->groupIds = $attributes->get('group_id');\n $instance->userIds = $attributes->get('user_id');\n $instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');\n $instance->participantUserIds = $attributes->get('participant_user_id');\n $instance->teamMemberUserIds = $attributes->get('team_member_user_id');\n $instance->excludedUserId = $attributes->get('excluded_user_id');\n $instance->activityTypeIds = $attributes->get('category_id');\n $instance->searchQuery = $attributes->get('query');\n $instance->transcriptKeywords = $attributes->get('transcript_keywords');\n $instance->transcriptSaidBy = $attributes->get('transcript_said_by');\n $instance->transcriptSpeaker = $attributes->get('transcript_speaker');\n $instance->languages = $attributes->get('languages');\n $instance->topicIds = $attributes->get('topic_id', []);\n $instance->compareTopicId = $attributes->get('compare_topic_id');\n $instance->stageIds = $attributes->get('stage_id');\n $instance->currentStageIds = $attributes->get('current_stage_id');\n $instance->playlistIds = $attributes->get('playlist_id');\n $instance->providerId = $attributes->get('provider_id');\n $instance->channelId = $attributes->get('channel_id');\n $instance->channelIds = $attributes->get('channel_ids');\n $hasTranscription = $attributes->get('has_transcription');\n $instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;\n $instance->externalId = $attributes->get('external_id');\n $instance->externalIdType = $attributes->get('external_id_type');\n $instance->notLogged = $attributes->get('not_logged') === '1';\n $instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';\n $instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';\n $instance->onlyRecorded = $attributes->has('only_recorded')\n ? (int) $attributes->get('only_recorded')\n : null;\n $instance->includeInternalConversations = $attributes->has('include_internal_conversations')\n ? (int) $attributes->get('include_internal_conversations')\n : null;\n $instance->minDuration = $attributes->has('min_duration')\n ? (int) $attributes->get('min_duration')\n : null;\n $instance->maxDuration = $attributes->has('max_duration')\n ? (int) $attributes->get('max_duration')\n : null;\n $minDealValue = $attributes->get('min_deal_value');\n $instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;\n $maxDealValue = $attributes->get('max_deal_value');\n $instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;\n $instance->minDealAge = $attributes->get('min_deal_age');\n $instance->maxDealAge = $attributes->get('max_deal_age');\n $instance->minCustomerMonologue = $attributes->get('min_customer_monologue');\n $instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');\n $instance->minMonologue = $attributes->get('min_monologue');\n $instance->maxMonologue = $attributes->get('max_monologue');\n $instance->minTalkRatio = $attributes->has('min_talk_ratio')\n ? (int) $attributes->get('min_talk_ratio')\n : null;\n $instance->maxTalkRatio = $attributes->has('max_talk_ratio')\n ? (int) $attributes->get('max_talk_ratio')\n : null;\n $instance->minPatience = $attributes->get('min_patience');\n $instance->maxPatience = $attributes->get('max_patience');\n $instance->minSpeechRate = $attributes->get('min_speech_rate');\n $instance->maxSpeechRate = $attributes->get('max_speech_rate');\n $instance->minScore = $attributes->has('min_score')\n ? (float) $attributes->get('min_score')\n : null;\n $instance->maxScore = $attributes->has('max_score')\n ? (float) $attributes->get('max_score')\n : null;\n\n $coachingScores = $attributes->get('coaching_score');\n if (is_array($coachingScores)) {\n $instance->coachingScores = array_map('intval', $coachingScores);\n }\n\n $instance->minAutoScore = $attributes->has('min_auto_score')\n ? (int) $attributes->get('min_auto_score')\n : null;\n $instance->maxAutoScore = $attributes->has('max_auto_score')\n ? (int) $attributes->get('max_auto_score')\n : null;\n\n $autoScores = $attributes->get('auto_score');\n if (is_array($autoScores)) {\n $instance->autoScores = array_map('intval', $autoScores);\n }\n\n $aiCallScores = $attributes->get('ai_call_score');\n if (is_array($aiCallScores)) {\n $instance->aiCallScores = array_map('intval', $aiCallScores);\n }\n $instance->minUserQuestions = $attributes->get('min_user_questions');\n $instance->maxUserQuestions = $attributes->get('max_user_questions');\n $instance->minEngagingQuestions = $attributes->get('min_engaging_questions');\n $instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');\n $instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');\n $instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');\n $instance->minCustomerQuestions = $attributes->get('min_customer_questions');\n $instance->maxCustomerQuestions = $attributes->get('max_customer_questions');\n $instance->nudgeRunId = $attributes->get('nudge_run_id');\n $instance->activityId = $attributes->get('activity_id');\n $instance->minCommentCount = $attributes->has('min_comment_count')\n ? (string) $attributes->get('min_comment_count')\n : null;\n $instance->maxCommentCount = $attributes->has('max_comment_count')\n ? (string) $attributes->get('max_comment_count')\n : null;\n $instance->onlyActiveUsers = $attributes->has('only_active_users')\n ? $attributes->get('only_active_users') === '1'\n : null;\n\n $instance->crmFieldValues = $attributes\n ->filter(static function ($value, $key): bool {\n return str_starts_with($key, FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX);\n })\n ->mapWithKeys(static function ($value, string $key): array {\n $key = str_replace_first(FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX, '', $key);\n\n return [\n $key => $value,\n ];\n })\n ->all();\n\n $instance->limit = (int) $attributes->get('limit', 25);\n $instance->pageNumber = (int) $attributes->get('page', 1);\n $instance->empty = $attributes->isEmpty();\n $instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);\n\n $instance->includeStatuses = $attributes->get('status');\n\n $instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);\n $instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);\n\n $dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;\n $dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;\n\n $instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);\n $instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);\n\n if ($attributes->has('deal_pipeline_id')) {\n $instance->dealPipelineIds = $attributes->get('deal_pipeline_id');\n }\n\n if ($attributes->has('deal_stage_id')) {\n $instance->dealStageIds = $attributes->get('deal_stage_id');\n }\n\n if ($attributes->has('deal_type_id')) {\n $instance->dealTypeIds = $attributes->get('deal_type_id');\n }\n\n if ($attributes->has('start_date')) {\n $instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);\n }\n\n if ($attributes->has('end_date')) {\n $instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);\n }\n\n if ($attributes->has('scheduled_from')) {\n $instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);\n }\n\n if ($attributes->has('scheduled_to')) {\n $instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);\n }\n\n if ($attributes->has('updated_from')) {\n $instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);\n }\n\n if ($attributes->has('updated_to')) {\n $instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);\n }\n\n $instance->sortBy = $attributes->get('sort_by');\n $instance->sortDirection = $attributes->get('sort_direction');\n\n return $instance;\n }\n\n public function setContext(?string $context): self\n {\n $this->context = $context;\n\n return $this;\n }\n\n public function getContext(): ?string\n {\n return $this->context;\n }\n\n public function getMinDealValue(): ?int\n {\n return $this->minDealValue;\n }\n\n public function getMaxDealValue(): ?int\n {\n return $this->maxDealValue;\n }\n\n public function hasMinDealAge(): bool\n {\n return $this->minDealAge !== null;\n }\n\n public function getMinDealAge(): int\n {\n return (int) $this->minDealAge;\n }\n\n public function hasMaxDealAge(): bool\n {\n return $this->maxDealAge !== null;\n }\n\n public function getMaxDealAge(): int\n {\n return (int) $this->maxDealAge;\n }\n\n public function hasTranscriptKeywords(): bool\n {\n return $this->transcriptKeywords !== null;\n }\n\n public function getTranscriptKeywords(): string\n {\n return $this->transcriptKeywords;\n }\n\n public function hasTranscriptSaidBy(): bool\n {\n return $this->transcriptSaidBy !== null;\n }\n\n public function getTranscriptSaidBy(): string\n {\n return $this->transcriptSaidBy;\n }\n\n public function hasTranscriptSpeaker(): bool\n {\n return $this->transcriptSpeaker !== null;\n }\n\n public function getTranscriptSpeaker(): string\n {\n return $this->transcriptSpeaker;\n }\n\n public function getLanguages(): ?array\n {\n return $this->languages;\n }\n\n public function getPartnerId(): ?string\n {\n return $this->partnerId;\n }\n\n public function getMinScore(): ?float\n {\n return $this->minScore;\n }\n\n public function getMaxScore(): ?float\n {\n return $this->maxScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getCoachingScores(): ?array\n {\n return $this->coachingScores;\n }\n\n public function getMinAutoScore(): ?int\n {\n return $this->minAutoScore;\n }\n\n public function getMaxAutoScore(): ?int\n {\n return $this->maxAutoScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getAutoScores(): ?array\n {\n return $this->autoScores;\n }\n\n /**\n * @return int[]|null\n */\n public function getAiCallScores(): ?array\n {\n return $this->aiCallScores;\n }\n\n public function hasMinPatience(): bool\n {\n return $this->minPatience !== null;\n }\n\n public function getMinPatience(): float\n {\n return (float) $this->minPatience;\n }\n\n public function hasMaxPatience(): bool\n {\n return $this->maxPatience !== null;\n }\n\n public function getMaxPatience(): float\n {\n return (float) $this->maxPatience;\n }\n\n public function hasMinSpeechRate(): bool\n {\n return $this->minSpeechRate !== null;\n }\n\n public function getMinSpeechRate(): float\n {\n return (float) $this->minSpeechRate;\n }\n\n public function hasMaxSpeechRate(): bool\n {\n return $this->maxSpeechRate !== null;\n }\n\n public function getMaxSpeechRate(): float\n {\n return (float) $this->maxSpeechRate;\n }\n\n public function getMinTalkRatio(): ?int\n {\n return $this->minTalkRatio;\n }\n\n public function getMaxTalkRatio(): ?int\n {\n return $this->maxTalkRatio;\n }\n\n public function hasMinMonologue(): bool\n {\n return $this->minMonologue !== null;\n }\n\n public function getMinMonologue(): float\n {\n return (float) $this->minMonologue;\n }\n\n public function hasMaxMonologue(): bool\n {\n return $this->maxMonologue !== null;\n }\n\n public function getMaxMonologue(): float\n {\n return (float) $this->maxMonologue;\n }\n\n public function hasMinCustomerMonologue(): bool\n {\n return $this->minCustomerMonologue !== null;\n }\n\n public function getMinCustomerMonologue(): float\n {\n return (float) $this->minCustomerMonologue;\n }\n\n public function hasMaxCustomerMonologue(): bool\n {\n return $this->maxCustomerMonologue !== null;\n }\n\n public function getMaxCustomerMonologue(): float\n {\n return (float) $this->maxCustomerMonologue;\n }\n\n public function getDealCloseDateStart(): ?CarbonImmutable\n {\n return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;\n }\n\n public function getDealCloseDateEnd(): ?CarbonImmutable\n {\n return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;\n }\n\n public function getDealCreatedDateStart(): ?CarbonImmutable\n {\n return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;\n }\n\n public function getDealCreatedDateEnd(): ?CarbonImmutable\n {\n return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;\n }\n\n /**\n * Check if deal close date start parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateStart(): bool\n {\n return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal close date end parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateEnd(): bool\n {\n return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date start parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateStart(): bool\n {\n return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date end parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateEnd(): bool\n {\n return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n public function hasDealStageIds(): bool\n {\n return $this->dealStageIds !== null;\n }\n\n public function getDealStageIds(): array\n {\n return $this->dealStageIds;\n }\n\n public function hasDealPipelineIds(): bool\n {\n return $this->dealPipelineIds !== null;\n }\n\n public function getDealPipelineIds(): array\n {\n return $this->dealPipelineIds;\n }\n\n public function hasDealTypeIds(): bool\n {\n return $this->dealTypeIds !== null;\n }\n\n public function getDealTypeIds(): array\n {\n return $this->dealTypeIds;\n }\n\n public function getExternalId(): ?string\n {\n return $this->externalId;\n }\n\n public function getExternalIdType(): ?string\n {\n return $this->externalIdType;\n }\n\n public function hasNudgeRunId(): bool\n {\n return $this->nudgeRunId !== null;\n }\n\n public function getNudgeRunId(): string\n {\n return $this->nudgeRunId;\n }\n\n public function hasActivityId(): bool\n {\n return $this->activityId !== null;\n }\n\n public function getActivityId(): string\n {\n return $this->activityId;\n }\n\n public function hasPlaylists(): bool\n {\n return $this->playlistIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getPlaylistIds(): array\n {\n return $this->playlistIds;\n }\n\n public function hasActivityTypeIds(): bool\n {\n return $this->activityTypeIds !== null;\n }\n\n public function getActivityTypeIds(): array\n {\n return $this->activityTypeIds;\n }\n\n /**\n * @return string[]\n */\n public function getTopicIds(): array\n {\n return $this->topicIds;\n }\n\n public function getCompareTopicId(): ?string\n {\n return $this->compareTopicId;\n }\n\n public function hasMinDuration(): bool\n {\n return $this->minDuration !== null;\n }\n\n public function getMinDuration(): int\n {\n return $this->minDuration;\n }\n\n public function hasMaxDuration(): bool\n {\n return $this->maxDuration !== null;\n }\n\n public function getMaxDuration(): int\n {\n return $this->maxDuration;\n }\n\n public function hasStageIds(): bool\n {\n return $this->stageIds !== null;\n }\n\n public function getStageIds(): array\n {\n return $this->stageIds;\n }\n\n public function hasCurrentStageIds(): bool\n {\n return $this->currentStageIds !== null;\n }\n\n public function getCurrentStageIds(): array\n {\n return $this->currentStageIds;\n }\n\n public function hasProviderId(): bool\n {\n return $this->providerId !== null;\n }\n\n public function getProviderId(): string\n {\n return $this->providerId;\n }\n\n public function getChannelId(): ?string\n {\n return $this->channelId;\n }\n\n public function getChannelIds(): ?array\n {\n return $this->channelIds;\n }\n\n public function getHasTranscription(): ?bool\n {\n return $this->hasTranscription;\n }\n\n /**\n * @return string[]|null\n */\n public function getGroupIds(): ?array\n {\n return $this->groupIds;\n }\n\n /** @return array<string, string|string[]> */\n public function getCrmFieldValues(): array\n {\n return $this->crmFieldValues;\n }\n\n public function hasCoachingFeedbackCoachUserIds(): bool\n {\n return $this->coachingFeedbackCoachUserId !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamIds(): array\n {\n return $this->teamIds;\n }\n\n public function hasTeamIds(): bool\n {\n return $this->teamIds !== null;\n }\n\n public function hasTeamMemberUserIds(): bool\n {\n return $this->teamMemberUserIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamMemberUserIds(): array\n {\n return $this->teamMemberUserIds;\n }\n\n /**\n * @return ?string[]\n */\n public function getUserIds(): ?array\n {\n return $this->userIds;\n }\n\n public function getCoachingFeedbackCoachUserId(): array\n {\n return $this->coachingFeedbackCoachUserId;\n }\n\n public function getParticipantUserIds(): ?array\n {\n return $this->participantUserIds;\n }\n\n\n public function hasExcludedUserId(): bool\n {\n return $this->excludedUserId !== null;\n }\n\n public function getExcludedUserId(): string\n {\n return $this->excludedUserId;\n }\n\n public function getStartDate(): ?CarbonImmutable\n {\n return $this->startDate;\n }\n\n public function getEndDate(): ?CarbonImmutable\n {\n return $this->endDate;\n }\n\n public function getScheduledFrom(): ?Carbon\n {\n return $this->scheduledFrom;\n }\n\n public function getScheduledTo(): ?Carbon\n {\n return $this->scheduledTo;\n }\n\n public function hasUpdatedFrom(): bool\n {\n return $this->updatedFrom !== null;\n }\n\n public function getUpdatedFrom(): Carbon\n {\n return $this->updatedFrom;\n }\n\n public function hasUpdatedTo(): bool\n {\n return $this->updatedTo !== null;\n }\n\n public function getUpdatedTo(): Carbon\n {\n return $this->updatedTo;\n }\n\n public function hasActivityStatusValues(): bool\n {\n return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;\n }\n\n /**\n * @return string[]\n */\n public function getActivityStatusValues(): array\n {\n return $this->includeStatuses;\n }\n\n public function onlyNotLoggedActivities(): bool\n {\n return $this->notLogged === true;\n }\n\n public function hasPendingAiCrmNotes(): bool\n {\n return $this->hasPendingAiCrmNotes === true;\n }\n\n public function hasIncludeHostJoinedMeetings(): bool\n {\n return $this->includeHostJoinedMeetings === true;\n }\n\n public function hasOnlyRecordedActivities(): bool\n {\n return $this->onlyRecorded !== null;\n }\n\n public function getOnlyRecordedActivities(): int\n {\n return $this->onlyRecorded;\n }\n\n public function getIncludeInternalConversations(): ?int\n {\n return $this->includeInternalConversations;\n }\n\n public function hasSearchQuery(): bool\n {\n return $this->searchQuery !== null;\n }\n\n public function getPageNumber(): int\n {\n return $this->pageNumber;\n }\n\n public function getSearchQuery(): ?string\n {\n return $this->searchQuery;\n }\n\n public function hasMinUserQuestions(): bool\n {\n return $this->minUserQuestions !== null;\n }\n\n public function getMinUserQuestions(): int\n {\n return (int) $this->minUserQuestions;\n }\n\n public function hasMaxUserQuestions(): bool\n {\n return $this->maxUserQuestions !== null;\n }\n\n public function getMaxUserQuestions(): int\n {\n return (int) $this->maxUserQuestions;\n }\n\n public function hasMinEngagingQuestions(): bool\n {\n return $this->minEngagingQuestions !== null;\n }\n\n public function getMinEngagingQuestions(): int\n {\n return (int) $this->minEngagingQuestions;\n }\n\n public function hasMaxEngagingQuestions(): bool\n {\n return $this->maxEngagingQuestions !== null;\n }\n\n public function getMaxEngagingQuestions(): int\n {\n return (int) $this->maxEngagingQuestions;\n }\n\n public function hasMinInsightfulQuestions(): bool\n {\n return $this->minInsightfulQuestions !== null;\n }\n\n public function getMinInsightfulQuestions(): int\n {\n return (int) $this->minInsightfulQuestions;\n }\n\n public function hasMaxInsightfulQuestions(): bool\n {\n return $this->maxInsightfulQuestions !== null;\n }\n\n public function getMaxInsightfulQuestions(): int\n {\n return (int) $this->maxInsightfulQuestions;\n }\n\n public function hasMinCustomerQuestions(): bool\n {\n return $this->minCustomerQuestions !== null;\n }\n\n public function getMinCustomerQuestions(): int\n {\n return (int) $this->minCustomerQuestions;\n }\n\n public function hasMaxCustomerQuestions(): bool\n {\n return $this->maxCustomerQuestions !== null;\n }\n\n public function getMaxCustomerQuestions(): int\n {\n return (int) $this->maxCustomerQuestions;\n }\n\n public function hasMinCommentCount(): bool\n {\n return $this->minCommentCount !== null;\n }\n\n public function getMinCommentCount(): string\n {\n return $this->minCommentCount;\n }\n\n public function hasMaxCommentCount(): bool\n {\n return $this->maxCommentCount !== null;\n }\n\n public function getMaxCommentCount(): string\n {\n return $this->maxCommentCount;\n }\n\n public function hasSortDirection(): bool\n {\n return $this->sortDirection !== null;\n }\n\n public function getSortDirection(): string\n {\n return $this->sortDirection;\n }\n\n public function hasSortBy(): bool\n {\n return $this->sortBy !== null;\n }\n\n public function getSortBy(): string\n {\n return $this->sortBy;\n }\n\n public function getLimit(): int\n {\n return $this->limit;\n }\n\n public function isEmpty(): bool\n {\n return $this->empty;\n }\n\n public function isFirstRequest(): bool\n {\n return $this->empty || $this->sequenceNumber === 0;\n }\n\n public function getOnlyActiveUsers(): bool\n {\n return $this->onlyActiveUsers === true;\n }\n\n /**\n * Parse a Deal Insights date from request attributes, handling null/empty values gracefully.\n * This method is specifically for Deal Insights date fields to support the \"All time\" filter.\n *\n * @param Collection $attributes\n * @param string $key\n * @param DateTimeZone $timezone\n *\n * @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is\n * missing, null if explicitly null, CarbonImmutable if valid date\n */\n private static function parseDealInsightsDate(\n Collection $attributes,\n string $key,\n DateTimeZone $timezone\n ): CarbonImmutable|string|null {\n if (! $attributes->has($key)) {\n return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)\n }\n\n $value = $attributes->get($key);\n\n if (empty($value) || $value === 'null') {\n return null; // Parameter is explicitly null (should use \"All time\")\n }\n\n return CarbonImmutable::parse($value, $timezone);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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},{"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, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"EventSubscriber, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinition, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Service","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityApiSearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"UserOptionsByGroup.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AbstractStageFilterDefinition.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearchServiceProvider.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactory.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactoryInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinition.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionCollection.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionQuery.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionQueryCollection.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilteredValueContainerInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"IntMinMaxRange.php, class","depth":10,"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":"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"}]...
|
-7241802098655403632
|
-3985712796493267083
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
isFirstRequest
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/1
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
46
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\VO\Repository\OnDemandActivitySearch;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\CreatedPeriodFilter;
class Criteria
{
/**
* Sentinel value to indicate a date parameter was not provided in the request
*/
private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';
/**
* @var string[]
*/
private $playlistIds;
/**
* @var string[]
*/
private array $topicIds = [];
private ?string $compareTopicId = null;
private ?string $partnerId;
/**
* @var string[]
*/
private $activityTypeIds;
/**
* @var string[]
*/
private $stageIds;
/**
* @var string[]
*/
private $currentStageIds;
/**
* @var string[]|null
*/
private ?array $groupIds = null;
/**
* @var string[]|null
*/
private ?array $userIds = null;
/**
* @var string[]|null
*/
private ?array $coachingFeedbackCoachUserId;
/**
* @var string[]|null
*/
private ?array $participantUserIds = null;
/**
* @var string[]|null
*/
private ?array $teamMemberUserIds;
/**
* @var string[]
*/
private $teamIds;
/**
* @var string
*/
private $providerId;
/**
* Channel ID for filtering activities by a single channel.
*/
private ?string $channelId = null;
/**
* Channel IDs for filtering activities by multiple channels.
* When provided, activities matching any of the specified channels will be returned.
*
* @var string[]|null
*/
private ?array $channelIds = null;
/**
* Filter activities based on whether they have a transcription.
* true = only activities with transcription, false = only activities without transcription, null = no filter
*/
private ?bool $hasTranscription = null;
private ?int $minDuration = null;
private ?int $maxDuration = null;
/**
* @var string
*
* @format uuid
*/
private $excludedUserId;
private ?CarbonImmutable $startDate = null;
private ?CarbonImmutable $endDate = null;
/**
* @var Carbon|null
*/
private $scheduledFrom;
private ?Carbon $scheduledTo = null;
/**
* @var Carbon|null
*/
private $updatedFrom;
/**
* @var Carbon|null
*/
private $updatedTo;
private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private ?array $dealStageIds = null;
private ?array $dealPipelineIds = null;
private ?array $dealTypeIds = null;
private ?int $minDealValue = null;
private ?int $maxDealValue = null;
/**
* @var string|null
*/
private $minDealAge;
/**
* @var string|null
*/
private $maxDealAge;
/**
* @var string|null
*/
private $minPatience;
/**
* @var string|null
*/
private $maxPatience;
private ?int $minTalkRatio = null;
private ?int $maxTalkRatio = null;
/**
* @var string|null
*/
private $minMonologue;
/**
* @var string|null
*/
private $maxMonologue;
/**
* @var string|null
*/
private $minCustomerMonologue;
/**
* @var string|null
*/
private $maxCustomerMonologue;
/**
* @var string|null
*/
private $minSpeechRate;
/**
* @var string|null
*/
private $maxSpeechRate;
private ?float $minScore = null;
private ?float $maxScore = null;
/**
* @var int[]|null
*/
private ?array $coachingScores = null;
private ?int $minAutoScore = null;
private ?int $maxAutoScore = null;
/**
* @var int[]|null
*/
private ?array $autoScores = null;
/**
* @var int[]|null
*/
private ?array $aiCallScores = null;
/**
* @var string[]
*/
private ?array $includeStatuses;
/**
* @var bool
*/
private $notLogged;
private bool $hasPendingAiCrmNotes;
private bool $includeHostJoinedMeetings;
private ?int $onlyRecorded;
private ?int $includeInternalConversations = null;
private ?string $searchQuery = null;
/**
* @var string
*/
private $transcriptKeywords;
/**
* @var string
*/
private $transcriptSaidBy;
/**
* @var string
*/
private $transcriptSpeaker;
/**
* @var string[]|null
*/
private $languages;
private ?string $externalId = null;
private ?string $externalIdType = null;
/**
* @var string|null
*/
private $minUserQuestions;
/**
* @var string|null
*/
private $maxUserQuestions;
/**
* @var string|null
*/
private $minEngagingQuestions;
/**
* @var string|null
*/
private $maxEngagingQuestions;
/**
* @var string|null
*/
private $minInsightfulQuestions;
/**
* @var string|null
*/
private $maxInsightfulQuestions;
/**
* @var string|null
*/
private $minCustomerQuestions;
/**
* @var string|null
*/
private $maxCustomerQuestions;
/**
* @var string|null
*/
private $sortDirection;
/**
* @var string|null
*/
private $sortBy;
private int $limit;
/**
* @var int
*/
private $pageNumber;
/**
* @var bool
*/
private $empty;
/**
* @var int
*/
private $sequenceNumber;
/**
* @var string
*/
private $nudgeRunId;
private ?bool $onlyActiveUsers = null;
/**
* @var string
*/
private $activityId;
private ?string $context = null;
private ?string $minCommentCount = null;
private ?string $maxCommentCount = null;
/** @var array<string, string|string[]> */
private array $crmFieldValues = [];
public static function createFromRequest(
array $requestAttributes,
DateTimeZone $timezone,
?string $context = null
): self {
$attributes = Collection::make($requestAttributes);
$instance = new self();
$instance->context = $context;
$instance->partnerId = $attributes->get('partner_id');
$instance->teamIds = $attributes->get('team_id');
$instance->groupIds = $attributes->get('group_id');
$instance->userIds = $attributes->get('user_id');
$instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');
$instance->participantUserIds = $attributes->get('participant_user_id');
$instance->teamMemberUserIds = $attributes->get('team_member_user_id');
$instance->excludedUserId = $attributes->get('excluded_user_id');
$instance->activityTypeIds = $attributes->get('category_id');
$instance->searchQuery = $attributes->get('query');
$instance->transcriptKeywords = $attributes->get('transcript_keywords');
$instance->transcriptSaidBy = $attributes->get('transcript_said_by');
$instance->transcriptSpeaker = $attributes->get('transcript_speaker');
$instance->languages = $attributes->get('languages');
$instance->topicIds = $attributes->get('topic_id', []);
$instance->compareTopicId = $attributes->get('compare_topic_id');
$instance->stageIds = $attributes->get('stage_id');
$instance->currentStageIds = $attributes->get('current_stage_id');
$instance->playlistIds = $attributes->get('playlist_id');
$instance->providerId = $attributes->get('provider_id');
$instance->channelId = $attributes->get('channel_id');
$instance->channelIds = $attributes->get('channel_ids');
$hasTranscription = $attributes->get('has_transcription');
$instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;
$instance->externalId = $attributes->get('external_id');
$instance->externalIdType = $attributes->get('external_id_type');
$instance->notLogged = $attributes->get('not_logged') === '1';
$instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';
$instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';
$instance->onlyRecorded = $attributes->has('only_recorded')
? (int) $attributes->get('only_recorded')
: null;
$instance->includeInternalConversations = $attributes->has('include_internal_conversations')
? (int) $attributes->get('include_internal_conversations')
: null;
$instance->minDuration = $attributes->has('min_duration')
? (int) $attributes->get('min_duration')
: null;
$instance->maxDuration = $attributes->has('max_duration')
? (int) $attributes->get('max_duration')
: null;
$minDealValue = $attributes->get('min_deal_value');
$instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;
$maxDealValue = $attributes->get('max_deal_value');
$instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;
$instance->minDealAge = $attributes->get('min_deal_age');
$instance->maxDealAge = $attributes->get('max_deal_age');
$instance->minCustomerMonologue = $attributes->get('min_customer_monologue');
$instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');
$instance->minMonologue = $attributes->get('min_monologue');
$instance->maxMonologue = $attributes->get('max_monologue');
$instance->minTalkRatio = $attributes->has('min_talk_ratio')
? (int) $attributes->get('min_talk_ratio')
: null;
$instance->maxTalkRatio = $attributes->has('max_talk_ratio')
? (int) $attributes->get('max_talk_ratio')
: null;
$instance->minPatience = $attributes->get('min_patience');
$instance->maxPatience = $attributes->get('max_patience');
$instance->minSpeechRate = $attributes->get('min_speech_rate');
$instance->maxSpeechRate = $attributes->get('max_speech_rate');
$instance->minScore = $attributes->has('min_score')
? (float) $attributes->get('min_score')
: null;
$instance->maxScore = $attributes->has('max_score')
? (float) $attributes->get('max_score')
: null;
$coachingScores = $attributes->get('coaching_score');
if (is_array($coachingScores)) {
$instance->coachingScores = array_map('intval', $coachingScores);
}
$instance->minAutoScore = $attributes->has('min_auto_score')
? (int) $attributes->get('min_auto_score')
: null;
$instance->maxAutoScore = $attributes->has('max_auto_score')
? (int) $attributes->get('max_auto_score')
: null;
$autoScores = $attributes->get('auto_score');
if (is_array($autoScores)) {
$instance->autoScores = array_map('intval', $autoScores);
}
$aiCallScores = $attributes->get('ai_call_score');
if (is_array($aiCallScores)) {
$instance->aiCallScores = array_map('intval', $aiCallScores);
}
$instance->minUserQuestions = $attributes->get('min_user_questions');
$instance->maxUserQuestions = $attributes->get('max_user_questions');
$instance->minEngagingQuestions = $attributes->get('min_engaging_questions');
$instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');
$instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');
$instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');
$instance->minCustomerQuestions = $attributes->get('min_customer_questions');
$instance->maxCustomerQuestions = $attributes->get('max_customer_questions');
$instance->nudgeRunId = $attributes->get('nudge_run_id');
$instance->activityId = $attributes->get('activity_id');
$instance->minCommentCount = $attributes->has('min_comment_count')
? (string) $attributes->get('min_comment_count')
: null;
$instance->maxCommentCount = $attributes->has('max_comment_count')
? (string) $attributes->get('max_comment_count')
: null;
$instance->onlyActiveUsers = $attributes->has('only_active_users')
? $attributes->get('only_active_users') === '1'
: null;
$instance->crmFieldValues = $attributes
->filter(static function ($value, $key): bool {
return str_starts_with($key, FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX);
})
->mapWithKeys(static function ($value, string $key): array {
$key = str_replace_first(FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX, '', $key);
return [
$key => $value,
];
})
->all();
$instance->limit = (int) $attributes->get('limit', 25);
$instance->pageNumber = (int) $attributes->get('page', 1);
$instance->empty = $attributes->isEmpty();
$instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);
$instance->includeStatuses = $attributes->get('status');
$instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);
$instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);
$dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;
$dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;
$instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);
$instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);
if ($attributes->has('deal_pipeline_id')) {
$instance->dealPipelineIds = $attributes->get('deal_pipeline_id');
}
if ($attributes->has('deal_stage_id')) {
$instance->dealStageIds = $attributes->get('deal_stage_id');
}
if ($attributes->has('deal_type_id')) {
$instance->dealTypeIds = $attributes->get('deal_type_id');
}
if ($attributes->has('start_date')) {
$instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);
}
if ($attributes->has('end_date')) {
$instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);
}
if ($attributes->has('scheduled_from')) {
$instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);
}
if ($attributes->has('scheduled_to')) {
$instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);
}
if ($attributes->has('updated_from')) {
$instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);
}
if ($attributes->has('updated_to')) {
$instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);
}
$instance->sortBy = $attributes->get('sort_by');
$instance->sortDirection = $attributes->get('sort_direction');
return $instance;
}
public function setContext(?string $context): self
{
$this->context = $context;
return $this;
}
public function getContext(): ?string
{
return $this->context;
}
public function getMinDealValue(): ?int
{
return $this->minDealValue;
}
public function getMaxDealValue(): ?int
{
return $this->maxDealValue;
}
public function hasMinDealAge(): bool
{
return $this->minDealAge !== null;
}
public function getMinDealAge(): int
{
return (int) $this->minDealAge;
}
public function hasMaxDealAge(): bool
{
return $this->maxDealAge !== null;
}
public function getMaxDealAge(): int
{
return (int) $this->maxDealAge;
}
public function hasTranscriptKeywords(): bool
{
return $this->transcriptKeywords !== null;
}
public function getTranscriptKeywords(): string
{
return $this->transcriptKeywords;
}
public function hasTranscriptSaidBy(): bool
{
return $this->transcriptSaidBy !== null;
}
public function getTranscriptSaidBy(): string
{
return $this->transcriptSaidBy;
}
public function hasTranscriptSpeaker(): bool
{
return $this->transcriptSpeaker !== null;
}
public function getTranscriptSpeaker(): string
{
return $this->transcriptSpeaker;
}
public function getLanguages(): ?array
{
return $this->languages;
}
public function getPartnerId(): ?string
{
return $this->partnerId;
}
public function getMinScore(): ?float
{
return $this->minScore;
}
public function getMaxScore(): ?float
{
return $this->maxScore;
}
/**
* @return int[]|null
*/
public function getCoachingScores(): ?array
{
return $this->coachingScores;
}
public function getMinAutoScore(): ?int
{
return $this->minAutoScore;
}
public function getMaxAutoScore(): ?int
{
return $this->maxAutoScore;
}
/**
* @return int[]|null
*/
public function getAutoScores(): ?array
{
return $this->autoScores;
}
/**
* @return int[]|null
*/
public function getAiCallScores(): ?array
{
return $this->aiCallScores;
}
public function hasMinPatience(): bool
{
return $this->minPatience !== null;
}
public function getMinPatience(): float
{
return (float) $this->minPatience;
}
public function hasMaxPatience(): bool
{
return $this->maxPatience !== null;
}
public function getMaxPatience(): float
{
return (float) $this->maxPatience;
}
public function hasMinSpeechRate(): bool
{
return $this->minSpeechRate !== null;
}
public function getMinSpeechRate(): float
{
return (float) $this->minSpeechRate;
}
public function hasMaxSpeechRate(): bool
{
return $this->maxSpeechRate !== null;
}
public function getMaxSpeechRate(): float
{
return (float) $this->maxSpeechRate;
}
public function getMinTalkRatio(): ?int
{
return $this->minTalkRatio;
}
public function getMaxTalkRatio(): ?int
{
return $this->maxTalkRatio;
}
public function hasMinMonologue(): bool
{
return $this->minMonologue !== null;
}
public function getMinMonologue(): float
{
return (float) $this->minMonologue;
}
public function hasMaxMonologue(): bool
{
return $this->maxMonologue !== null;
}
public function getMaxMonologue(): float
{
return (float) $this->maxMonologue;
}
public function hasMinCustomerMonologue(): bool
{
return $this->minCustomerMonologue !== null;
}
public function getMinCustomerMonologue(): float
{
return (float) $this->minCustomerMonologue;
}
public function hasMaxCustomerMonologue(): bool
{
return $this->maxCustomerMonologue !== null;
}
public function getMaxCustomerMonologue(): float
{
return (float) $this->maxCustomerMonologue;
}
public function getDealCloseDateStart(): ?CarbonImmutable
{
return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;
}
public function getDealCloseDateEnd(): ?CarbonImmutable
{
return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;
}
public function getDealCreatedDateStart(): ?CarbonImmutable
{
return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;
}
public function getDealCreatedDateEnd(): ?CarbonImmutable
{
return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;
}
/**
* Check if deal close date start parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateStart(): bool
{
return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal close date end parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateEnd(): bool
{
return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date start parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateStart(): bool
{
return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date end parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateEnd(): bool
{
return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
public function hasDealStageIds(): bool
{
return $this->dealStageIds !== null;
}
public function getDealStageIds(): array
{
return $this->dealStageIds;
}
public function hasDealPipelineIds(): bool
{
return $this->dealPipelineIds !== null;
}
public function getDealPipelineIds(): array
{
return $this->dealPipelineIds;
}
public function hasDealTypeIds(): bool
{
return $this->dealTypeIds !== null;
}
public function getDealTypeIds(): array
{
return $this->dealTypeIds;
}
public function getExternalId(): ?string
{
return $this->externalId;
}
public function getExternalIdType(): ?string
{
return $this->externalIdType;
}
public function hasNudgeRunId(): bool
{
return $this->nudgeRunId !== null;
}
public function getNudgeRunId(): string
{
return $this->nudgeRunId;
}
public function hasActivityId(): bool
{
return $this->activityId !== null;
}
public function getActivityId(): string
{
return $this->activityId;
}
public function hasPlaylists(): bool
{
return $this->playlistIds !== null;
}
/**
* @return string[]
*/
public function getPlaylistIds(): array
{
return $this->playlistIds;
}
public function hasActivityTypeIds(): bool
{
return $this->activityTypeIds !== null;
}
public function getActivityTypeIds(): array
{
return $this->activityTypeIds;
}
/**
* @return string[]
*/
public function getTopicIds(): array
{
return $this->topicIds;
}
public function getCompareTopicId(): ?string
{
return $this->compareTopicId;
}
public function hasMinDuration(): bool
{
return $this->minDuration !== null;
}
public function getMinDuration(): int
{
return $this->minDuration;
}
public function hasMaxDuration(): bool
{
return $this->maxDuration !== null;
}
public function getMaxDuration(): int
{
return $this->maxDuration;
}
public function hasStageIds(): bool
{
return $this->stageIds !== null;
}
public function getStageIds(): array
{
return $this->stageIds;
}
public function hasCurrentStageIds(): bool
{
return $this->currentStageIds !== null;
}
public function getCurrentStageIds(): array
{
return $this->currentStageIds;
}
public function hasProviderId(): bool
{
return $this->providerId !== null;
}
public function getProviderId(): string
{
return $this->providerId;
}
public function getChannelId(): ?string
{
return $this->channelId;
}
public function getChannelIds(): ?array
{
return $this->channelIds;
}
public function getHasTranscription(): ?bool
{
return $this->hasTranscription;
}
/**
* @return string[]|null
*/
public function getGroupIds(): ?array
{
return $this->groupIds;
}
/** @return array<string, string|string[]> */
public function getCrmFieldValues(): array
{
return $this->crmFieldValues;
}
public function hasCoachingFeedbackCoachUserIds(): bool
{
return $this->coachingFeedbackCoachUserId !== null;
}
/**
* @return string[]
*/
public function getTeamIds(): array
{
return $this->teamIds;
}
public function hasTeamIds(): bool
{
return $this->teamIds !== null;
}
public function hasTeamMemberUserIds(): bool
{
return $this->teamMemberUserIds !== null;
}
/**
* @return string[]
*/
public function getTeamMemberUserIds(): array
{
return $this->teamMemberUserIds;
}
/**
* @return ?string[]
*/
public function getUserIds(): ?array
{
return $this->userIds;
}
public function getCoachingFeedbackCoachUserId(): array
{
return $this->coachingFeedbackCoachUserId;
}
public function getParticipantUserIds(): ?array
{
return $this->participantUserIds;
}
public function hasExcludedUserId(): bool
{
return $this->excludedUserId !== null;
}
public function getExcludedUserId(): string
{
return $this->excludedUserId;
}
public function getStartDate(): ?CarbonImmutable
{
return $this->startDate;
}
public function getEndDate(): ?CarbonImmutable
{
return $this->endDate;
}
public function getScheduledFrom(): ?Carbon
{
return $this->scheduledFrom;
}
public function getScheduledTo(): ?Carbon
{
return $this->scheduledTo;
}
public function hasUpdatedFrom(): bool
{
return $this->updatedFrom !== null;
}
public function getUpdatedFrom(): Carbon
{
return $this->updatedFrom;
}
public function hasUpdatedTo(): bool
{
return $this->updatedTo !== null;
}
public function getUpdatedTo(): Carbon
{
return $this->updatedTo;
}
public function hasActivityStatusValues(): bool
{
return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;
}
/**
* @return string[]
*/
public function getActivityStatusValues(): array
{
return $this->includeStatuses;
}
public function onlyNotLoggedActivities(): bool
{
return $this->notLogged === true;
}
public function hasPendingAiCrmNotes(): bool
{
return $this->hasPendingAiCrmNotes === true;
}
public function hasIncludeHostJoinedMeetings(): bool
{
return $this->includeHostJoinedMeetings === true;
}
public function hasOnlyRecordedActivities(): bool
{
return $this->onlyRecorded !== null;
}
public function getOnlyRecordedActivities(): int
{
return $this->onlyRecorded;
}
public function getIncludeInternalConversations(): ?int
{
return $this->includeInternalConversations;
}
public function hasSearchQuery(): bool
{
return $this->searchQuery !== null;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
public function getSearchQuery(): ?string
{
return $this->searchQuery;
}
public function hasMinUserQuestions(): bool
{
return $this->minUserQuestions !== null;
}
public function getMinUserQuestions(): int
{
return (int) $this->minUserQuestions;
}
public function hasMaxUserQuestions(): bool
{
return $this->maxUserQuestions !== null;
}
public function getMaxUserQuestions(): int
{
return (int) $this->maxUserQuestions;
}
public function hasMinEngagingQuestions(): bool
{
return $this->minEngagingQuestions !== null;
}
public function getMinEngagingQuestions(): int
{
return (int) $this->minEngagingQuestions;
}
public function hasMaxEngagingQuestions(): bool
{
return $this->maxEngagingQuestions !== null;
}
public function getMaxEngagingQuestions(): int
{
return (int) $this->maxEngagingQuestions;
}
public function hasMinInsightfulQuestions(): bool
{
return $this->minInsightfulQuestions !== null;
}
public function getMinInsightfulQuestions(): int
{
return (int) $this->minInsightfulQuestions;
}
public function hasMaxInsightfulQuestions(): bool
{
return $this->maxInsightfulQuestions !== null;
}
public function getMaxInsightfulQuestions(): int
{
return (int) $this->maxInsightfulQuestions;
}
public function hasMinCustomerQuestions(): bool
{
return $this->minCustomerQuestions !== null;
}
public function getMinCustomerQuestions(): int
{
return (int) $this->minCustomerQuestions;
}
public function hasMaxCustomerQuestions(): bool
{
return $this->maxCustomerQuestions !== null;
}
public function getMaxCustomerQuestions(): int
{
return (int) $this->maxCustomerQuestions;
}
public function hasMinCommentCount(): bool
{
return $this->minCommentCount !== null;
}
public function getMinCommentCount(): string
{
return $this->minCommentCount;
}
public function hasMaxCommentCount(): bool
{
return $this->maxCommentCount !== null;
}
public function getMaxCommentCount(): string
{
return $this->maxCommentCount;
}
public function hasSortDirection(): bool
{
return $this->sortDirection !== null;
}
public function getSortDirection(): string
{
return $this->sortDirection;
}
public function hasSortBy(): bool
{
return $this->sortBy !== null;
}
public function getSortBy(): string
{
return $this->sortBy;
}
public function getLimit(): int
{
return $this->limit;
}
public function isEmpty(): bool
{
return $this->empty;
}
public function isFirstRequest(): bool
{
return $this->empty || $this->sequenceNumber === 0;
}
public function getOnlyActiveUsers(): bool
{
return $this->onlyActiveUsers === true;
}
/**
* Parse a Deal Insights date from request attributes, handling null/empty values gracefully.
* This method is specifically for Deal Insights date fields to support the "All time" filter.
*
* @param Collection $attributes
* @param string $key
* @param DateTimeZone $timezone
*
* @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is
* missing, null if explicitly null, CarbonImmutable if valid date
*/
private static function parseDealInsightsDate(
Collection $attributes,
string $key,
DateTimeZone $timezone
): CarbonImmutable|string|null {
if (! $attributes->has($key)) {
return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)
}
$value = $attributes->get($key);
if (empty($value) || $value === 'null') {
return null; // Parameter is explicitly null (should use "All time")
}
return CarbonImmutable::parse($value, $timezone);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
EventSubscriber, folder
FilterDefinition, folder
Service
ActivityApiSearch.php, class
ActivitySearch.php, class
UserOptionsByGroup.php, class
AbstractStageFilterDefinition.php, abstract class
ActivitySearchServiceProvider.php, final class
DealInsightsPeriodFilterFactory.php
DealInsightsPeriodFilterFactoryInterface.php, interface
FilterDefinition.php, abstract class
FilterDefinitionCollection.php, class
FilterDefinitionQuery.php, class
FilterDefinitionQueryCollection.php, class
FilteredValueContainerInterface.php, interface
IntMinMaxRange.php, class
AiActivityType
AiAutomation
AiCallScoring
AskAnything
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country...
|
NULL
|
|
11037
|
218
|
7
|
2026-04-14T09:09:51.619166+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157791619_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5186 0Today ~...
|
NULL
|
-1962154817434150537
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5186 0Today ~...
|
11035
|
|
11039
|
218
|
8
|
2026-04-14T09:09:53.061300+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157793061_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5286 0Today ~...
|
NULL
|
2677305522102540400
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5286 0Today ~...
|
NULL
|
|
11041
|
218
|
9
|
2026-04-14T09:09:56.403830+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157796403_m1.jpg...
|
PhpStorm
|
faVsco.js – Criteria.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
isFirstRequest
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/1
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
46
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\VO\Repository\OnDemandActivitySearch;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\CreatedPeriodFilter;
class Criteria
{
/**
* Sentinel value to indicate a date parameter was not provided in the request
*/
private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';
/**
* @var string[]
*/
private $playlistIds;
/**
* @var string[]
*/
private array $topicIds = [];
private ?string $compareTopicId = null;
private ?string $partnerId;
/**
* @var string[]
*/
private $activityTypeIds;
/**
* @var string[]
*/
private $stageIds;
/**
* @var string[]
*/
private $currentStageIds;
/**
* @var string[]|null
*/
private ?array $groupIds = null;
/**
* @var string[]|null
*/
private ?array $userIds = null;
/**
* @var string[]|null
*/
private ?array $coachingFeedbackCoachUserId;
/**
* @var string[]|null
*/
private ?array $participantUserIds = null;
/**
* @var string[]|null
*/
private ?array $teamMemberUserIds;
/**
* @var string[]
*/
private $teamIds;
/**
* @var string
*/
private $providerId;
/**
* Channel ID for filtering activities by a single channel.
*/
private ?string $channelId = null;
/**
* Channel IDs for filtering activities by multiple channels.
* When provided, activities matching any of the specified channels will be returned.
*
* @var string[]|null
*/
private ?array $channelIds = null;
/**
* Filter activities based on whether they have a transcription.
* true = only activities with transcription, false = only activities without transcription, null = no filter
*/
private ?bool $hasTranscription = null;
private ?int $minDuration = null;
private ?int $maxDuration = null;
/**
* @var string
*
* @format uuid
*/
private $excludedUserId;
private ?CarbonImmutable $startDate = null;
private ?CarbonImmutable $endDate = null;
/**
* @var Carbon|null
*/
private $scheduledFrom;
private ?Carbon $scheduledTo = null;
/**
* @var Carbon|null
*/
private $updatedFrom;
/**
* @var Carbon|null
*/
private $updatedTo;
private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private ?array $dealStageIds = null;
private ?array $dealPipelineIds = null;
private ?array $dealTypeIds = null;
private ?int $minDealValue = null;
private ?int $maxDealValue = null;
/**
* @var string|null
*/
private $minDealAge;
/**
* @var string|null
*/
private $maxDealAge;
/**
* @var string|null
*/
private $minPatience;
/**
* @var string|null
*/
private $maxPatience;
private ?int $minTalkRatio = null;
private ?int $maxTalkRatio = null;
/**
* @var string|null
*/
private $minMonologue;
/**
* @var string|null
*/
private $maxMonologue;
/**
* @var string|null
*/
private $minCustomerMonologue;
/**
* @var string|null
*/
private $maxCustomerMonologue;
/**
* @var string|null
*/
private $minSpeechRate;
/**
* @var string|null
*/
private $maxSpeechRate;
private ?float $minScore = null;
private ?float $maxScore = null;
/**
* @var int[]|null
*/
private ?array $coachingScores = null;
private ?int $minAutoScore = null;
private ?int $maxAutoScore = null;
/**
* @var int[]|null
*/
private ?array $autoScores = null;
/**
* @var int[]|null
*/
private ?array $aiCallScores = null;
/**
* @var string[]
*/
private ?array $includeStatuses;
/**
* @var bool
*/
private $notLogged;
private bool $hasPendingAiCrmNotes;
private bool $includeHostJoinedMeetings;
private ?int $onlyRecorded;
private ?int $includeInternalConversations = null;
private ?string $searchQuery = null;
/**
* @var string
*/
private $transcriptKeywords;
/**
* @var string
*/
private $transcriptSaidBy;
/**
* @var string
*/
private $transcriptSpeaker;
/**
* @var string[]|null
*/
private $languages;
private ?string $externalId = null;
private ?string $externalIdType = null;
/**
* @var string|null
*/
private $minUserQuestions;
/**
* @var string|null
*/
private $maxUserQuestions;
/**
* @var string|null
*/
private $minEngagingQuestions;
/**
* @var string|null
*/
private $maxEngagingQuestions;
/**
* @var string|null
*/
private $minInsightfulQuestions;
/**
* @var string|null
*/
private $maxInsightfulQuestions;
/**
* @var string|null
*/
private $minCustomerQuestions;
/**
* @var string|null
*/
private $maxCustomerQuestions;
/**
* @var string|null
*/
private $sortDirection;
/**
* @var string|null
*/
private $sortBy;
private int $limit;
/**
* @var int
*/
private $pageNumber;
/**
* @var bool
*/
private $empty;
/**
* @var int
*/
private $sequenceNumber;
/**
* @var string
*/
private $nudgeRunId;
private ?bool $onlyActiveUsers = null;
/**
* @var string
*/
private $activityId;
private ?string $context = null;
private ?string $minCommentCount = null;
private ?string $maxCommentCount = null;
/** @var array<string, string|string[]> */
private array $crmFieldValues = [];
public static function createFromRequest(
array $requestAttributes,
DateTimeZone $timezone,
?string $context = null
): self {
$attributes = Collection::make($requestAttributes);
$instance = new self();
$instance->context = $context;
$instance->partnerId = $attributes->get('partner_id');
$instance->teamIds = $attributes->get('team_id');
$instance->groupIds = $attributes->get('group_id');
$instance->userIds = $attributes->get('user_id');
$instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');
$instance->participantUserIds = $attributes->get('participant_user_id');
$instance->teamMemberUserIds = $attributes->get('team_member_user_id');
$instance->excludedUserId = $attributes->get('excluded_user_id');
$instance->activityTypeIds = $attributes->get('category_id');
$instance->searchQuery = $attributes->get('query');
$instance->transcriptKeywords = $attributes->get('transcript_keywords');
$instance->transcriptSaidBy = $attributes->get('transcript_said_by');
$instance->transcriptSpeaker = $attributes->get('transcript_speaker');
$instance->languages = $attributes->get('languages');
$instance->topicIds = $attributes->get('topic_id', []);
$instance->compareTopicId = $attributes->get('compare_topic_id');
$instance->stageIds = $attributes->get('stage_id');
$instance->currentStageIds = $attributes->get('current_stage_id');
$instance->playlistIds = $attributes->get('playlist_id');
$instance->providerId = $attributes->get('provider_id');
$instance->channelId = $attributes->get('channel_id');
$instance->channelIds = $attributes->get('channel_ids');
$hasTranscription = $attributes->get('has_transcription');
$instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;
$instance->externalId = $attributes->get('external_id');
$instance->externalIdType = $attributes->get('external_id_type');
$instance->notLogged = $attributes->get('not_logged') === '1';
$instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';
$instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';
$instance->onlyRecorded = $attributes->has('only_recorded')
? (int) $attributes->get('only_recorded')
: null;
$instance->includeInternalConversations = $attributes->has('include_internal_conversations')
? (int) $attributes->get('include_internal_conversations')
: null;
$instance->minDuration = $attributes->has('min_duration')
? (int) $attributes->get('min_duration')
: null;
$instance->maxDuration = $attributes->has('max_duration')
? (int) $attributes->get('max_duration')
: null;
$minDealValue = $attributes->get('min_deal_value');
$instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;
$maxDealValue = $attributes->get('max_deal_value');
$instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;
$instance->minDealAge = $attributes->get('min_deal_age');
$instance->maxDealAge = $attributes->get('max_deal_age');
$instance->minCustomerMonologue = $attributes->get('min_customer_monologue');
$instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');
$instance->minMonologue = $attributes->get('min_monologue');
$instance->maxMonologue = $attributes->get('max_monologue');
$instance->minTalkRatio = $attributes->has('min_talk_ratio')
? (int) $attributes->get('min_talk_ratio')
: null;
$instance->maxTalkRatio = $attributes->has('max_talk_ratio')
? (int) $attributes->get('max_talk_ratio')
: null;
$instance->minPatience = $attributes->get('min_patience');
$instance->maxPatience = $attributes->get('max_patience');
$instance->minSpeechRate = $attributes->get('min_speech_rate');
$instance->maxSpeechRate = $attributes->get('max_speech_rate');
$instance->minScore = $attributes->has('min_score')
? (float) $attributes->get('min_score')
: null;
$instance->maxScore = $attributes->has('max_score')
? (float) $attributes->get('max_score')
: null;
$coachingScores = $attributes->get('coaching_score');
if (is_array($coachingScores)) {
$instance->coachingScores = array_map('intval', $coachingScores);
}
$instance->minAutoScore = $attributes->has('min_auto_score')
? (int) $attributes->get('min_auto_score')
: null;
$instance->maxAutoScore = $attributes->has('max_auto_score')
? (int) $attributes->get('max_auto_score')
: null;
$autoScores = $attributes->get('auto_score');
if (is_array($autoScores)) {
$instance->autoScores = array_map('intval', $autoScores);
}
$aiCallScores = $attributes->get('ai_call_score');
if (is_array($aiCallScores)) {
$instance->aiCallScores = array_map('intval', $aiCallScores);
}
$instance->minUserQuestions = $attributes->get('min_user_questions');
$instance->maxUserQuestions = $attributes->get('max_user_questions');
$instance->minEngagingQuestions = $attributes->get('min_engaging_questions');
$instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');
$instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');
$instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');
$instance->minCustomerQuestions = $attributes->get('min_customer_questions');
$instance->maxCustomerQuestions = $attributes->get('max_customer_questions');
$instance->nudgeRunId = $attributes->get('nudge_run_id');
$instance->activityId = $attributes->get('activity_id');
$instance->minCommentCount = $attributes->has('min_comment_count')
? (string) $attributes->get('min_comment_count')
: null;
$instance->maxCommentCount = $attributes->has('max_comment_count')
? (string) $attributes->get('max_comment_count')
: null;
$instance->onlyActiveUsers = $attributes->has('only_active_users')
? $attributes->get('only_active_users') === '1'
: null;
$instance->crmFieldValues = $attributes
->filter(static function ($value, $key): bool {
return str_starts_with($key, FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX);
})
->mapWithKeys(static function ($value, string $key): array {
$key = str_replace_first(FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX, '', $key);
return [
$key => $value,
];
})
->all();
$instance->limit = (int) $attributes->get('limit', 25);
$instance->pageNumber = (int) $attributes->get('page', 1);
$instance->empty = $attributes->isEmpty();
$instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);
$instance->includeStatuses = $attributes->get('status');
$instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);
$instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);
$dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;
$dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;
$instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);
$instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);
if ($attributes->has('deal_pipeline_id')) {
$instance->dealPipelineIds = $attributes->get('deal_pipeline_id');
}
if ($attributes->has('deal_stage_id')) {
$instance->dealStageIds = $attributes->get('deal_stage_id');
}
if ($attributes->has('deal_type_id')) {
$instance->dealTypeIds = $attributes->get('deal_type_id');
}
if ($attributes->has('start_date')) {
$instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);
}
if ($attributes->has('end_date')) {
$instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);
}
if ($attributes->has('scheduled_from')) {
$instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);
}
if ($attributes->has('scheduled_to')) {
$instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);
}
if ($attributes->has('updated_from')) {
$instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);
}
if ($attributes->has('updated_to')) {
$instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);
}
$instance->sortBy = $attributes->get('sort_by');
$instance->sortDirection = $attributes->get('sort_direction');
return $instance;
}
public function setContext(?string $context): self
{
$this->context = $context;
return $this;
}
public function getContext(): ?string
{
return $this->context;
}
public function getMinDealValue(): ?int
{
return $this->minDealValue;
}
public function getMaxDealValue(): ?int
{
return $this->maxDealValue;
}
public function hasMinDealAge(): bool
{
return $this->minDealAge !== null;
}
public function getMinDealAge(): int
{
return (int) $this->minDealAge;
}
public function hasMaxDealAge(): bool
{
return $this->maxDealAge !== null;
}
public function getMaxDealAge(): int
{
return (int) $this->maxDealAge;
}
public function hasTranscriptKeywords(): bool
{
return $this->transcriptKeywords !== null;
}
public function getTranscriptKeywords(): string
{
return $this->transcriptKeywords;
}
public function hasTranscriptSaidBy(): bool
{
return $this->transcriptSaidBy !== null;
}
public function getTranscriptSaidBy(): string
{
return $this->transcriptSaidBy;
}
public function hasTranscriptSpeaker(): bool
{
return $this->transcriptSpeaker !== null;
}
public function getTranscriptSpeaker(): string
{
return $this->transcriptSpeaker;
}
public function getLanguages(): ?array
{
return $this->languages;
}
public function getPartnerId(): ?string
{
return $this->partnerId;
}
public function getMinScore(): ?float
{
return $this->minScore;
}
public function getMaxScore(): ?float
{
return $this->maxScore;
}
/**
* @return int[]|null
*/
public function getCoachingScores(): ?array
{
return $this->coachingScores;
}
public function getMinAutoScore(): ?int
{
return $this->minAutoScore;
}
public function getMaxAutoScore(): ?int
{
return $this->maxAutoScore;
}
/**
* @return int[]|null
*/
public function getAutoScores(): ?array
{
return $this->autoScores;
}
/**
* @return int[]|null
*/
public function getAiCallScores(): ?array
{
return $this->aiCallScores;
}
public function hasMinPatience(): bool
{
return $this->minPatience !== null;
}
public function getMinPatience(): float
{
return (float) $this->minPatience;
}
public function hasMaxPatience(): bool
{
return $this->maxPatience !== null;
}
public function getMaxPatience(): float
{
return (float) $this->maxPatience;
}
public function hasMinSpeechRate(): bool
{
return $this->minSpeechRate !== null;
}
public function getMinSpeechRate(): float
{
return (float) $this->minSpeechRate;
}
public function hasMaxSpeechRate(): bool
{
return $this->maxSpeechRate !== null;
}
public function getMaxSpeechRate(): float
{
return (float) $this->maxSpeechRate;
}
public function getMinTalkRatio(): ?int
{
return $this->minTalkRatio;
}
public function getMaxTalkRatio(): ?int
{
return $this->maxTalkRatio;
}
public function hasMinMonologue(): bool
{
return $this->minMonologue !== null;
}
public function getMinMonologue(): float
{
return (float) $this->minMonologue;
}
public function hasMaxMonologue(): bool
{
return $this->maxMonologue !== null;
}
public function getMaxMonologue(): float
{
return (float) $this->maxMonologue;
}
public function hasMinCustomerMonologue(): bool
{
return $this->minCustomerMonologue !== null;
}
public function getMinCustomerMonologue(): float
{
return (float) $this->minCustomerMonologue;
}
public function hasMaxCustomerMonologue(): bool
{
return $this->maxCustomerMonologue !== null;
}
public function getMaxCustomerMonologue(): float
{
return (float) $this->maxCustomerMonologue;
}
public function getDealCloseDateStart(): ?CarbonImmutable
{
return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;
}
public function getDealCloseDateEnd(): ?CarbonImmutable
{
return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;
}
public function getDealCreatedDateStart(): ?CarbonImmutable
{
return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;
}
public function getDealCreatedDateEnd(): ?CarbonImmutable
{
return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;
}
/**
* Check if deal close date start parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateStart(): bool
{
return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal close date end parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateEnd(): bool
{
return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date start parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateStart(): bool
{
return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date end parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateEnd(): bool
{
return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
public function hasDealStageIds(): bool
{
return $this->dealStageIds !== null;
}
public function getDealStageIds(): array
{
return $this->dealStageIds;
}
public function hasDealPipelineIds(): bool
{
return $this->dealPipelineIds !== null;
}
public function getDealPipelineIds(): array
{
return $this->dealPipelineIds;
}
public function hasDealTypeIds(): bool
{
return $this->dealTypeIds !== null;
}
public function getDealTypeIds(): array
{
return $this->dealTypeIds;
}
public function getExternalId(): ?string
{
return $this->externalId;
}
public function getExternalIdType(): ?string
{
return $this->externalIdType;
}
public function hasNudgeRunId(): bool
{
return $this->nudgeRunId !== null;
}
public function getNudgeRunId(): string
{
return $this->nudgeRunId;
}
public function hasActivityId(): bool
{
return $this->activityId !== null;
}
public function getActivityId(): string
{
return $this->activityId;
}
public function hasPlaylists(): bool
{
return $this->playlistIds !== null;
}
/**
* @return string[]
*/
public function getPlaylistIds(): array
{
return $this->playlistIds;
}
public function hasActivityTypeIds(): bool
{
return $this->activityTypeIds !== null;
}
public function getActivityTypeIds(): array
{
return $this->activityTypeIds;
}
/**
* @return string[]
*/
public function getTopicIds(): array
{
return $this->topicIds;
}
public function getCompareTopicId(): ?string
{
return $this->compareTopicId;
}
public function hasMinDuration(): bool
{
return $this->minDuration !== null;
}
public function getMinDuration(): int
{
return $this->minDuration;
}
public function hasMaxDuration(): bool
{
return $this->maxDuration !== null;
}
public function getMaxDuration(): int
{
return $this->maxDuration;
}
public function hasStageIds(): bool
{
return $this->stageIds !== null;
}
public function getStageIds(): array
{
return $this->stageIds;
}
public function hasCurrentStageIds(): bool
{
return $this->currentStageIds !== null;
}
public function getCurrentStageIds(): array
{
return $this->currentStageIds;
}
public function hasProviderId(): bool
{
return $this->providerId !== null;
}
public function getProviderId(): string
{
return $this->providerId;
}
public function getChannelId(): ?string
{
return $this->channelId;
}
public function getChannelIds(): ?array
{
return $this->channelIds;
}
public function getHasTranscription(): ?bool
{
return $this->hasTranscription;
}
/**
* @return string[]|null
*/
public function getGroupIds(): ?array
{
return $this->groupIds;
}
/** @return array<string, string|string[]> */
public function getCrmFieldValues(): array
{
return $this->crmFieldValues;
}
public function hasCoachingFeedbackCoachUserIds(): bool
{
return $this->coachingFeedbackCoachUserId !== null;
}
/**
* @return string[]
*/
public function getTeamIds(): array
{
return $this->teamIds;
}
public function hasTeamIds(): bool
{
return $this->teamIds !== null;
}
public function hasTeamMemberUserIds(): bool
{
return $this->teamMemberUserIds !== null;
}
/**
* @return string[]
*/
public function getTeamMemberUserIds(): array
{
return $this->teamMemberUserIds;
}
/**
* @return ?string[]
*/
public function getUserIds(): ?array
{
return $this->userIds;
}
public function getCoachingFeedbackCoachUserId(): array
{
return $this->coachingFeedbackCoachUserId;
}
public function getParticipantUserIds(): ?array
{
return $this->participantUserIds;
}
public function hasExcludedUserId(): bool
{
return $this->excludedUserId !== null;
}
public function getExcludedUserId(): string
{
return $this->excludedUserId;
}
public function getStartDate(): ?CarbonImmutable
{
return $this->startDate;
}
public function getEndDate(): ?CarbonImmutable
{
return $this->endDate;
}
public function getScheduledFrom(): ?Carbon
{
return $this->scheduledFrom;
}
public function getScheduledTo(): ?Carbon
{
return $this->scheduledTo;
}
public function hasUpdatedFrom(): bool
{
return $this->updatedFrom !== null;
}
public function getUpdatedFrom(): Carbon
{
return $this->updatedFrom;
}
public function hasUpdatedTo(): bool
{
return $this->updatedTo !== null;
}
public function getUpdatedTo(): Carbon
{
return $this->updatedTo;
}
public function hasActivityStatusValues(): bool
{
return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;
}
/**
* @return string[]
*/
public function getActivityStatusValues(): array
{
return $this->includeStatuses;
}
public function onlyNotLoggedActivities(): bool
{
return $this->notLogged === true;
}
public function hasPendingAiCrmNotes(): bool
{
return $this->hasPendingAiCrmNotes === true;
}
public function hasIncludeHostJoinedMeetings(): bool
{
return $this->includeHostJoinedMeetings === true;
}
public function hasOnlyRecordedActivities(): bool
{
return $this->onlyRecorded !== null;
}
public function getOnlyRecordedActivities(): int
{
return $this->onlyRecorded;
}
public function getIncludeInternalConversations(): ?int
{
return $this->includeInternalConversations;
}
public function hasSearchQuery(): bool
{
return $this->searchQuery !== null;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
public function getSearchQuery(): ?string
{
return $this->searchQuery;
}
public function hasMinUserQuestions(): bool
{
return $this->minUserQuestions !== null;
}
public function getMinUserQuestions(): int
{
return (int) $this->minUserQuestions;
}
public function hasMaxUserQuestions(): bool
{
return $this->maxUserQuestions !== null;
}
public function getMaxUserQuestions(): int
{
return (int) $this->maxUserQuestions;
}
public function hasMinEngagingQuestions(): bool
{
return $this->minEngagingQuestions !== null;
}
public function getMinEngagingQuestions(): int
{
return (int) $this->minEngagingQuestions;
}
public function hasMaxEngagingQuestions(): bool
{
return $this->maxEngagingQuestions !== null;
}
public function getMaxEngagingQuestions(): int
{
return (int) $this->maxEngagingQuestions;
}
public function hasMinInsightfulQuestions(): bool
{
return $this->minInsightfulQuestions !== null;
}
public function getMinInsightfulQuestions(): int
{
return (int) $this->minInsightfulQuestions;
}
public function hasMaxInsightfulQuestions(): bool
{
return $this->maxInsightfulQuestions !== null;
}
public function getMaxInsightfulQuestions(): int
{
return (int) $this->maxInsightfulQuestions;
}
public function hasMinCustomerQuestions(): bool
{
return $this->minCustomerQuestions !== null;
}
public function getMinCustomerQuestions(): int
{
return (int) $this->minCustomerQuestions;
}
public function hasMaxCustomerQuestions(): bool
{
return $this->maxCustomerQuestions !== null;
}
public function getMaxCustomerQuestions(): int
{
return (int) $this->maxCustomerQuestions;
}
public function hasMinCommentCount(): bool
{
return $this->minCommentCount !== null;
}
public function getMinCommentCount(): string
{
return $this->minCommentCount;
}
public function hasMaxCommentCount(): bool
{
return $this->maxCommentCount !== null;
}
public function getMaxCommentCount(): string
{
return $this->maxCommentCount;
}
public function hasSortDirection(): bool
{
return $this->sortDirection !== null;
}
public function getSortDirection(): string
{
return $this->sortDirection;
}
public function hasSortBy(): bool
{
return $this->sortBy !== null;
}
public function getSortBy(): string
{
return $this->sortBy;
}
public function getLimit(): int
{
return $this->limit;
}
public function isEmpty(): bool
{
return $this->empty;
}
public function isFirstRequest(): bool
{
return $this->empty || $this->sequenceNumber === 0;
}
public function getOnlyActiveUsers(): bool
{
return $this->onlyActiveUsers === true;
}
/**
* Parse a Deal Insights date from request attributes, handling null/empty values gracefully.
* This method is specifically for Deal Insights date fields to support the "All time" filter.
*
* @param Collection $attributes
* @param string $key
* @param DateTimeZone $timezone
*
* @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is
* missing, null if explicitly null, CarbonImmutable if valid date
*/
private static function parseDealInsightsDate(
Collection $attributes,
string $key,
DateTimeZone $timezone
): CarbonImmutable|string|null {
if (! $attributes->has($key)) {
return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)
}
$value = $attributes->get($key);
if (empty($value) || $value === 'null') {
return null; // Parameter is explicitly null (should use "All time")
}
return CarbonImmutable::parse($value, $timezone);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
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, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"isFirstRequest","depth":4,"value":"isFirstRequest","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1/1","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"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":"46","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","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\\VO\\Repository\\OnDemandActivitySearch;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse DateTimeZone;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\CreatedPeriodFilter;\n\nclass Criteria\n{\n /**\n * Sentinel value to indicate a date parameter was not provided in the request\n */\n private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';\n\n /**\n * @var string[]\n */\n private $playlistIds;\n\n /**\n * @var string[]\n */\n private array $topicIds = [];\n\n private ?string $compareTopicId = null;\n\n private ?string $partnerId;\n\n /**\n * @var string[]\n */\n private $activityTypeIds;\n\n /**\n * @var string[]\n */\n private $stageIds;\n\n /**\n * @var string[]\n */\n private $currentStageIds;\n\n /**\n * @var string[]|null\n */\n private ?array $groupIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $userIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $coachingFeedbackCoachUserId;\n\n /**\n * @var string[]|null\n */\n private ?array $participantUserIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $teamMemberUserIds;\n\n /**\n * @var string[]\n */\n private $teamIds;\n\n /**\n * @var string\n */\n private $providerId;\n\n /**\n * Channel ID for filtering activities by a single channel.\n */\n private ?string $channelId = null;\n\n /**\n * Channel IDs for filtering activities by multiple channels.\n * When provided, activities matching any of the specified channels will be returned.\n *\n * @var string[]|null\n */\n private ?array $channelIds = null;\n\n /**\n * Filter activities based on whether they have a transcription.\n * true = only activities with transcription, false = only activities without transcription, null = no filter\n */\n private ?bool $hasTranscription = null;\n\n private ?int $minDuration = null;\n private ?int $maxDuration = null;\n\n /**\n * @var string\n *\n * @format uuid\n */\n private $excludedUserId;\n\n private ?CarbonImmutable $startDate = null;\n private ?CarbonImmutable $endDate = null;\n\n /**\n * @var Carbon|null\n */\n private $scheduledFrom;\n\n private ?Carbon $scheduledTo = null;\n\n /**\n * @var Carbon|null\n */\n private $updatedFrom;\n\n /**\n * @var Carbon|null\n */\n private $updatedTo;\n\n private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private ?array $dealStageIds = null;\n private ?array $dealPipelineIds = null;\n private ?array $dealTypeIds = null;\n\n private ?int $minDealValue = null;\n private ?int $maxDealValue = null;\n\n /**\n * @var string|null\n */\n private $minDealAge;\n\n /**\n * @var string|null\n */\n private $maxDealAge;\n\n /**\n * @var string|null\n */\n private $minPatience;\n\n /**\n * @var string|null\n */\n private $maxPatience;\n\n private ?int $minTalkRatio = null;\n private ?int $maxTalkRatio = null;\n\n /**\n * @var string|null\n */\n private $minMonologue;\n\n /**\n * @var string|null\n */\n private $maxMonologue;\n\n /**\n * @var string|null\n */\n private $minCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $maxCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $minSpeechRate;\n\n /**\n * @var string|null\n */\n private $maxSpeechRate;\n\n private ?float $minScore = null;\n private ?float $maxScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $coachingScores = null;\n\n private ?int $minAutoScore = null;\n private ?int $maxAutoScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $autoScores = null;\n\n /**\n * @var int[]|null\n */\n private ?array $aiCallScores = null;\n\n /**\n * @var string[]\n */\n private ?array $includeStatuses;\n\n /**\n * @var bool\n */\n private $notLogged;\n\n private bool $hasPendingAiCrmNotes;\n\n private bool $includeHostJoinedMeetings;\n\n private ?int $onlyRecorded;\n\n private ?int $includeInternalConversations = null;\n\n private ?string $searchQuery = null;\n\n /**\n * @var string\n */\n private $transcriptKeywords;\n\n /**\n * @var string\n */\n private $transcriptSaidBy;\n\n /**\n * @var string\n */\n private $transcriptSpeaker;\n\n /**\n * @var string[]|null\n */\n private $languages;\n\n private ?string $externalId = null;\n private ?string $externalIdType = null;\n\n /**\n * @var string|null\n */\n private $minUserQuestions;\n\n /**\n * @var string|null\n */\n private $maxUserQuestions;\n\n /**\n * @var string|null\n */\n private $minEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $maxEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $minInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $maxInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $minCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $maxCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $sortDirection;\n\n /**\n * @var string|null\n */\n private $sortBy;\n\n private int $limit;\n\n /**\n * @var int\n */\n private $pageNumber;\n\n /**\n * @var bool\n */\n private $empty;\n\n /**\n * @var int\n */\n private $sequenceNumber;\n\n /**\n * @var string\n */\n private $nudgeRunId;\n\n private ?bool $onlyActiveUsers = null;\n\n /**\n * @var string\n */\n private $activityId;\n\n private ?string $context = null;\n\n private ?string $minCommentCount = null;\n private ?string $maxCommentCount = null;\n /** @var array<string, string|string[]> */\n private array $crmFieldValues = [];\n\n public static function createFromRequest(\n array $requestAttributes,\n DateTimeZone $timezone,\n ?string $context = null\n ): self {\n $attributes = Collection::make($requestAttributes);\n\n $instance = new self();\n $instance->context = $context;\n\n $instance->partnerId = $attributes->get('partner_id');\n $instance->teamIds = $attributes->get('team_id');\n $instance->groupIds = $attributes->get('group_id');\n $instance->userIds = $attributes->get('user_id');\n $instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');\n $instance->participantUserIds = $attributes->get('participant_user_id');\n $instance->teamMemberUserIds = $attributes->get('team_member_user_id');\n $instance->excludedUserId = $attributes->get('excluded_user_id');\n $instance->activityTypeIds = $attributes->get('category_id');\n $instance->searchQuery = $attributes->get('query');\n $instance->transcriptKeywords = $attributes->get('transcript_keywords');\n $instance->transcriptSaidBy = $attributes->get('transcript_said_by');\n $instance->transcriptSpeaker = $attributes->get('transcript_speaker');\n $instance->languages = $attributes->get('languages');\n $instance->topicIds = $attributes->get('topic_id', []);\n $instance->compareTopicId = $attributes->get('compare_topic_id');\n $instance->stageIds = $attributes->get('stage_id');\n $instance->currentStageIds = $attributes->get('current_stage_id');\n $instance->playlistIds = $attributes->get('playlist_id');\n $instance->providerId = $attributes->get('provider_id');\n $instance->channelId = $attributes->get('channel_id');\n $instance->channelIds = $attributes->get('channel_ids');\n $hasTranscription = $attributes->get('has_transcription');\n $instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;\n $instance->externalId = $attributes->get('external_id');\n $instance->externalIdType = $attributes->get('external_id_type');\n $instance->notLogged = $attributes->get('not_logged') === '1';\n $instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';\n $instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';\n $instance->onlyRecorded = $attributes->has('only_recorded')\n ? (int) $attributes->get('only_recorded')\n : null;\n $instance->includeInternalConversations = $attributes->has('include_internal_conversations')\n ? (int) $attributes->get('include_internal_conversations')\n : null;\n $instance->minDuration = $attributes->has('min_duration')\n ? (int) $attributes->get('min_duration')\n : null;\n $instance->maxDuration = $attributes->has('max_duration')\n ? (int) $attributes->get('max_duration')\n : null;\n $minDealValue = $attributes->get('min_deal_value');\n $instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;\n $maxDealValue = $attributes->get('max_deal_value');\n $instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;\n $instance->minDealAge = $attributes->get('min_deal_age');\n $instance->maxDealAge = $attributes->get('max_deal_age');\n $instance->minCustomerMonologue = $attributes->get('min_customer_monologue');\n $instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');\n $instance->minMonologue = $attributes->get('min_monologue');\n $instance->maxMonologue = $attributes->get('max_monologue');\n $instance->minTalkRatio = $attributes->has('min_talk_ratio')\n ? (int) $attributes->get('min_talk_ratio')\n : null;\n $instance->maxTalkRatio = $attributes->has('max_talk_ratio')\n ? (int) $attributes->get('max_talk_ratio')\n : null;\n $instance->minPatience = $attributes->get('min_patience');\n $instance->maxPatience = $attributes->get('max_patience');\n $instance->minSpeechRate = $attributes->get('min_speech_rate');\n $instance->maxSpeechRate = $attributes->get('max_speech_rate');\n $instance->minScore = $attributes->has('min_score')\n ? (float) $attributes->get('min_score')\n : null;\n $instance->maxScore = $attributes->has('max_score')\n ? (float) $attributes->get('max_score')\n : null;\n\n $coachingScores = $attributes->get('coaching_score');\n if (is_array($coachingScores)) {\n $instance->coachingScores = array_map('intval', $coachingScores);\n }\n\n $instance->minAutoScore = $attributes->has('min_auto_score')\n ? (int) $attributes->get('min_auto_score')\n : null;\n $instance->maxAutoScore = $attributes->has('max_auto_score')\n ? (int) $attributes->get('max_auto_score')\n : null;\n\n $autoScores = $attributes->get('auto_score');\n if (is_array($autoScores)) {\n $instance->autoScores = array_map('intval', $autoScores);\n }\n\n $aiCallScores = $attributes->get('ai_call_score');\n if (is_array($aiCallScores)) {\n $instance->aiCallScores = array_map('intval', $aiCallScores);\n }\n $instance->minUserQuestions = $attributes->get('min_user_questions');\n $instance->maxUserQuestions = $attributes->get('max_user_questions');\n $instance->minEngagingQuestions = $attributes->get('min_engaging_questions');\n $instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');\n $instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');\n $instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');\n $instance->minCustomerQuestions = $attributes->get('min_customer_questions');\n $instance->maxCustomerQuestions = $attributes->get('max_customer_questions');\n $instance->nudgeRunId = $attributes->get('nudge_run_id');\n $instance->activityId = $attributes->get('activity_id');\n $instance->minCommentCount = $attributes->has('min_comment_count')\n ? (string) $attributes->get('min_comment_count')\n : null;\n $instance->maxCommentCount = $attributes->has('max_comment_count')\n ? (string) $attributes->get('max_comment_count')\n : null;\n $instance->onlyActiveUsers = $attributes->has('only_active_users')\n ? $attributes->get('only_active_users') === '1'\n : null;\n\n $instance->crmFieldValues = $attributes\n ->filter(static function ($value, $key): bool {\n return str_starts_with($key, FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX);\n })\n ->mapWithKeys(static function ($value, string $key): array {\n $key = str_replace_first(FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX, '', $key);\n\n return [\n $key => $value,\n ];\n })\n ->all();\n\n $instance->limit = (int) $attributes->get('limit', 25);\n $instance->pageNumber = (int) $attributes->get('page', 1);\n $instance->empty = $attributes->isEmpty();\n $instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);\n\n $instance->includeStatuses = $attributes->get('status');\n\n $instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);\n $instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);\n\n $dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;\n $dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;\n\n $instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);\n $instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);\n\n if ($attributes->has('deal_pipeline_id')) {\n $instance->dealPipelineIds = $attributes->get('deal_pipeline_id');\n }\n\n if ($attributes->has('deal_stage_id')) {\n $instance->dealStageIds = $attributes->get('deal_stage_id');\n }\n\n if ($attributes->has('deal_type_id')) {\n $instance->dealTypeIds = $attributes->get('deal_type_id');\n }\n\n if ($attributes->has('start_date')) {\n $instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);\n }\n\n if ($attributes->has('end_date')) {\n $instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);\n }\n\n if ($attributes->has('scheduled_from')) {\n $instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);\n }\n\n if ($attributes->has('scheduled_to')) {\n $instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);\n }\n\n if ($attributes->has('updated_from')) {\n $instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);\n }\n\n if ($attributes->has('updated_to')) {\n $instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);\n }\n\n $instance->sortBy = $attributes->get('sort_by');\n $instance->sortDirection = $attributes->get('sort_direction');\n\n return $instance;\n }\n\n public function setContext(?string $context): self\n {\n $this->context = $context;\n\n return $this;\n }\n\n public function getContext(): ?string\n {\n return $this->context;\n }\n\n public function getMinDealValue(): ?int\n {\n return $this->minDealValue;\n }\n\n public function getMaxDealValue(): ?int\n {\n return $this->maxDealValue;\n }\n\n public function hasMinDealAge(): bool\n {\n return $this->minDealAge !== null;\n }\n\n public function getMinDealAge(): int\n {\n return (int) $this->minDealAge;\n }\n\n public function hasMaxDealAge(): bool\n {\n return $this->maxDealAge !== null;\n }\n\n public function getMaxDealAge(): int\n {\n return (int) $this->maxDealAge;\n }\n\n public function hasTranscriptKeywords(): bool\n {\n return $this->transcriptKeywords !== null;\n }\n\n public function getTranscriptKeywords(): string\n {\n return $this->transcriptKeywords;\n }\n\n public function hasTranscriptSaidBy(): bool\n {\n return $this->transcriptSaidBy !== null;\n }\n\n public function getTranscriptSaidBy(): string\n {\n return $this->transcriptSaidBy;\n }\n\n public function hasTranscriptSpeaker(): bool\n {\n return $this->transcriptSpeaker !== null;\n }\n\n public function getTranscriptSpeaker(): string\n {\n return $this->transcriptSpeaker;\n }\n\n public function getLanguages(): ?array\n {\n return $this->languages;\n }\n\n public function getPartnerId(): ?string\n {\n return $this->partnerId;\n }\n\n public function getMinScore(): ?float\n {\n return $this->minScore;\n }\n\n public function getMaxScore(): ?float\n {\n return $this->maxScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getCoachingScores(): ?array\n {\n return $this->coachingScores;\n }\n\n public function getMinAutoScore(): ?int\n {\n return $this->minAutoScore;\n }\n\n public function getMaxAutoScore(): ?int\n {\n return $this->maxAutoScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getAutoScores(): ?array\n {\n return $this->autoScores;\n }\n\n /**\n * @return int[]|null\n */\n public function getAiCallScores(): ?array\n {\n return $this->aiCallScores;\n }\n\n public function hasMinPatience(): bool\n {\n return $this->minPatience !== null;\n }\n\n public function getMinPatience(): float\n {\n return (float) $this->minPatience;\n }\n\n public function hasMaxPatience(): bool\n {\n return $this->maxPatience !== null;\n }\n\n public function getMaxPatience(): float\n {\n return (float) $this->maxPatience;\n }\n\n public function hasMinSpeechRate(): bool\n {\n return $this->minSpeechRate !== null;\n }\n\n public function getMinSpeechRate(): float\n {\n return (float) $this->minSpeechRate;\n }\n\n public function hasMaxSpeechRate(): bool\n {\n return $this->maxSpeechRate !== null;\n }\n\n public function getMaxSpeechRate(): float\n {\n return (float) $this->maxSpeechRate;\n }\n\n public function getMinTalkRatio(): ?int\n {\n return $this->minTalkRatio;\n }\n\n public function getMaxTalkRatio(): ?int\n {\n return $this->maxTalkRatio;\n }\n\n public function hasMinMonologue(): bool\n {\n return $this->minMonologue !== null;\n }\n\n public function getMinMonologue(): float\n {\n return (float) $this->minMonologue;\n }\n\n public function hasMaxMonologue(): bool\n {\n return $this->maxMonologue !== null;\n }\n\n public function getMaxMonologue(): float\n {\n return (float) $this->maxMonologue;\n }\n\n public function hasMinCustomerMonologue(): bool\n {\n return $this->minCustomerMonologue !== null;\n }\n\n public function getMinCustomerMonologue(): float\n {\n return (float) $this->minCustomerMonologue;\n }\n\n public function hasMaxCustomerMonologue(): bool\n {\n return $this->maxCustomerMonologue !== null;\n }\n\n public function getMaxCustomerMonologue(): float\n {\n return (float) $this->maxCustomerMonologue;\n }\n\n public function getDealCloseDateStart(): ?CarbonImmutable\n {\n return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;\n }\n\n public function getDealCloseDateEnd(): ?CarbonImmutable\n {\n return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;\n }\n\n public function getDealCreatedDateStart(): ?CarbonImmutable\n {\n return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;\n }\n\n public function getDealCreatedDateEnd(): ?CarbonImmutable\n {\n return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;\n }\n\n /**\n * Check if deal close date start parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateStart(): bool\n {\n return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal close date end parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateEnd(): bool\n {\n return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date start parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateStart(): bool\n {\n return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date end parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateEnd(): bool\n {\n return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n public function hasDealStageIds(): bool\n {\n return $this->dealStageIds !== null;\n }\n\n public function getDealStageIds(): array\n {\n return $this->dealStageIds;\n }\n\n public function hasDealPipelineIds(): bool\n {\n return $this->dealPipelineIds !== null;\n }\n\n public function getDealPipelineIds(): array\n {\n return $this->dealPipelineIds;\n }\n\n public function hasDealTypeIds(): bool\n {\n return $this->dealTypeIds !== null;\n }\n\n public function getDealTypeIds(): array\n {\n return $this->dealTypeIds;\n }\n\n public function getExternalId(): ?string\n {\n return $this->externalId;\n }\n\n public function getExternalIdType(): ?string\n {\n return $this->externalIdType;\n }\n\n public function hasNudgeRunId(): bool\n {\n return $this->nudgeRunId !== null;\n }\n\n public function getNudgeRunId(): string\n {\n return $this->nudgeRunId;\n }\n\n public function hasActivityId(): bool\n {\n return $this->activityId !== null;\n }\n\n public function getActivityId(): string\n {\n return $this->activityId;\n }\n\n public function hasPlaylists(): bool\n {\n return $this->playlistIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getPlaylistIds(): array\n {\n return $this->playlistIds;\n }\n\n public function hasActivityTypeIds(): bool\n {\n return $this->activityTypeIds !== null;\n }\n\n public function getActivityTypeIds(): array\n {\n return $this->activityTypeIds;\n }\n\n /**\n * @return string[]\n */\n public function getTopicIds(): array\n {\n return $this->topicIds;\n }\n\n public function getCompareTopicId(): ?string\n {\n return $this->compareTopicId;\n }\n\n public function hasMinDuration(): bool\n {\n return $this->minDuration !== null;\n }\n\n public function getMinDuration(): int\n {\n return $this->minDuration;\n }\n\n public function hasMaxDuration(): bool\n {\n return $this->maxDuration !== null;\n }\n\n public function getMaxDuration(): int\n {\n return $this->maxDuration;\n }\n\n public function hasStageIds(): bool\n {\n return $this->stageIds !== null;\n }\n\n public function getStageIds(): array\n {\n return $this->stageIds;\n }\n\n public function hasCurrentStageIds(): bool\n {\n return $this->currentStageIds !== null;\n }\n\n public function getCurrentStageIds(): array\n {\n return $this->currentStageIds;\n }\n\n public function hasProviderId(): bool\n {\n return $this->providerId !== null;\n }\n\n public function getProviderId(): string\n {\n return $this->providerId;\n }\n\n public function getChannelId(): ?string\n {\n return $this->channelId;\n }\n\n public function getChannelIds(): ?array\n {\n return $this->channelIds;\n }\n\n public function getHasTranscription(): ?bool\n {\n return $this->hasTranscription;\n }\n\n /**\n * @return string[]|null\n */\n public function getGroupIds(): ?array\n {\n return $this->groupIds;\n }\n\n /** @return array<string, string|string[]> */\n public function getCrmFieldValues(): array\n {\n return $this->crmFieldValues;\n }\n\n public function hasCoachingFeedbackCoachUserIds(): bool\n {\n return $this->coachingFeedbackCoachUserId !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamIds(): array\n {\n return $this->teamIds;\n }\n\n public function hasTeamIds(): bool\n {\n return $this->teamIds !== null;\n }\n\n public function hasTeamMemberUserIds(): bool\n {\n return $this->teamMemberUserIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamMemberUserIds(): array\n {\n return $this->teamMemberUserIds;\n }\n\n /**\n * @return ?string[]\n */\n public function getUserIds(): ?array\n {\n return $this->userIds;\n }\n\n public function getCoachingFeedbackCoachUserId(): array\n {\n return $this->coachingFeedbackCoachUserId;\n }\n\n public function getParticipantUserIds(): ?array\n {\n return $this->participantUserIds;\n }\n\n\n public function hasExcludedUserId(): bool\n {\n return $this->excludedUserId !== null;\n }\n\n public function getExcludedUserId(): string\n {\n return $this->excludedUserId;\n }\n\n public function getStartDate(): ?CarbonImmutable\n {\n return $this->startDate;\n }\n\n public function getEndDate(): ?CarbonImmutable\n {\n return $this->endDate;\n }\n\n public function getScheduledFrom(): ?Carbon\n {\n return $this->scheduledFrom;\n }\n\n public function getScheduledTo(): ?Carbon\n {\n return $this->scheduledTo;\n }\n\n public function hasUpdatedFrom(): bool\n {\n return $this->updatedFrom !== null;\n }\n\n public function getUpdatedFrom(): Carbon\n {\n return $this->updatedFrom;\n }\n\n public function hasUpdatedTo(): bool\n {\n return $this->updatedTo !== null;\n }\n\n public function getUpdatedTo(): Carbon\n {\n return $this->updatedTo;\n }\n\n public function hasActivityStatusValues(): bool\n {\n return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;\n }\n\n /**\n * @return string[]\n */\n public function getActivityStatusValues(): array\n {\n return $this->includeStatuses;\n }\n\n public function onlyNotLoggedActivities(): bool\n {\n return $this->notLogged === true;\n }\n\n public function hasPendingAiCrmNotes(): bool\n {\n return $this->hasPendingAiCrmNotes === true;\n }\n\n public function hasIncludeHostJoinedMeetings(): bool\n {\n return $this->includeHostJoinedMeetings === true;\n }\n\n public function hasOnlyRecordedActivities(): bool\n {\n return $this->onlyRecorded !== null;\n }\n\n public function getOnlyRecordedActivities(): int\n {\n return $this->onlyRecorded;\n }\n\n public function getIncludeInternalConversations(): ?int\n {\n return $this->includeInternalConversations;\n }\n\n public function hasSearchQuery(): bool\n {\n return $this->searchQuery !== null;\n }\n\n public function getPageNumber(): int\n {\n return $this->pageNumber;\n }\n\n public function getSearchQuery(): ?string\n {\n return $this->searchQuery;\n }\n\n public function hasMinUserQuestions(): bool\n {\n return $this->minUserQuestions !== null;\n }\n\n public function getMinUserQuestions(): int\n {\n return (int) $this->minUserQuestions;\n }\n\n public function hasMaxUserQuestions(): bool\n {\n return $this->maxUserQuestions !== null;\n }\n\n public function getMaxUserQuestions(): int\n {\n return (int) $this->maxUserQuestions;\n }\n\n public function hasMinEngagingQuestions(): bool\n {\n return $this->minEngagingQuestions !== null;\n }\n\n public function getMinEngagingQuestions(): int\n {\n return (int) $this->minEngagingQuestions;\n }\n\n public function hasMaxEngagingQuestions(): bool\n {\n return $this->maxEngagingQuestions !== null;\n }\n\n public function getMaxEngagingQuestions(): int\n {\n return (int) $this->maxEngagingQuestions;\n }\n\n public function hasMinInsightfulQuestions(): bool\n {\n return $this->minInsightfulQuestions !== null;\n }\n\n public function getMinInsightfulQuestions(): int\n {\n return (int) $this->minInsightfulQuestions;\n }\n\n public function hasMaxInsightfulQuestions(): bool\n {\n return $this->maxInsightfulQuestions !== null;\n }\n\n public function getMaxInsightfulQuestions(): int\n {\n return (int) $this->maxInsightfulQuestions;\n }\n\n public function hasMinCustomerQuestions(): bool\n {\n return $this->minCustomerQuestions !== null;\n }\n\n public function getMinCustomerQuestions(): int\n {\n return (int) $this->minCustomerQuestions;\n }\n\n public function hasMaxCustomerQuestions(): bool\n {\n return $this->maxCustomerQuestions !== null;\n }\n\n public function getMaxCustomerQuestions(): int\n {\n return (int) $this->maxCustomerQuestions;\n }\n\n public function hasMinCommentCount(): bool\n {\n return $this->minCommentCount !== null;\n }\n\n public function getMinCommentCount(): string\n {\n return $this->minCommentCount;\n }\n\n public function hasMaxCommentCount(): bool\n {\n return $this->maxCommentCount !== null;\n }\n\n public function getMaxCommentCount(): string\n {\n return $this->maxCommentCount;\n }\n\n public function hasSortDirection(): bool\n {\n return $this->sortDirection !== null;\n }\n\n public function getSortDirection(): string\n {\n return $this->sortDirection;\n }\n\n public function hasSortBy(): bool\n {\n return $this->sortBy !== null;\n }\n\n public function getSortBy(): string\n {\n return $this->sortBy;\n }\n\n public function getLimit(): int\n {\n return $this->limit;\n }\n\n public function isEmpty(): bool\n {\n return $this->empty;\n }\n\n public function isFirstRequest(): bool\n {\n return $this->empty || $this->sequenceNumber === 0;\n }\n\n public function getOnlyActiveUsers(): bool\n {\n return $this->onlyActiveUsers === true;\n }\n\n /**\n * Parse a Deal Insights date from request attributes, handling null/empty values gracefully.\n * This method is specifically for Deal Insights date fields to support the \"All time\" filter.\n *\n * @param Collection $attributes\n * @param string $key\n * @param DateTimeZone $timezone\n *\n * @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is\n * missing, null if explicitly null, CarbonImmutable if valid date\n */\n private static function parseDealInsightsDate(\n Collection $attributes,\n string $key,\n DateTimeZone $timezone\n ): CarbonImmutable|string|null {\n if (! $attributes->has($key)) {\n return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)\n }\n\n $value = $attributes->get($key);\n\n if (empty($value) || $value === 'null') {\n return null; // Parameter is explicitly null (should use \"All time\")\n }\n\n return CarbonImmutable::parse($value, $timezone);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\VO\\Repository\\OnDemandActivitySearch;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse DateTimeZone;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\CreatedPeriodFilter;\n\nclass Criteria\n{\n /**\n * Sentinel value to indicate a date parameter was not provided in the request\n */\n private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';\n\n /**\n * @var string[]\n */\n private $playlistIds;\n\n /**\n * @var string[]\n */\n private array $topicIds = [];\n\n private ?string $compareTopicId = null;\n\n private ?string $partnerId;\n\n /**\n * @var string[]\n */\n private $activityTypeIds;\n\n /**\n * @var string[]\n */\n private $stageIds;\n\n /**\n * @var string[]\n */\n private $currentStageIds;\n\n /**\n * @var string[]|null\n */\n private ?array $groupIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $userIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $coachingFeedbackCoachUserId;\n\n /**\n * @var string[]|null\n */\n private ?array $participantUserIds = null;\n\n /**\n * @var string[]|null\n */\n private ?array $teamMemberUserIds;\n\n /**\n * @var string[]\n */\n private $teamIds;\n\n /**\n * @var string\n */\n private $providerId;\n\n /**\n * Channel ID for filtering activities by a single channel.\n */\n private ?string $channelId = null;\n\n /**\n * Channel IDs for filtering activities by multiple channels.\n * When provided, activities matching any of the specified channels will be returned.\n *\n * @var string[]|null\n */\n private ?array $channelIds = null;\n\n /**\n * Filter activities based on whether they have a transcription.\n * true = only activities with transcription, false = only activities without transcription, null = no filter\n */\n private ?bool $hasTranscription = null;\n\n private ?int $minDuration = null;\n private ?int $maxDuration = null;\n\n /**\n * @var string\n *\n * @format uuid\n */\n private $excludedUserId;\n\n private ?CarbonImmutable $startDate = null;\n private ?CarbonImmutable $endDate = null;\n\n /**\n * @var Carbon|null\n */\n private $scheduledFrom;\n\n private ?Carbon $scheduledTo = null;\n\n /**\n * @var Carbon|null\n */\n private $updatedFrom;\n\n /**\n * @var Carbon|null\n */\n private $updatedTo;\n\n private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;\n private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;\n\n private ?array $dealStageIds = null;\n private ?array $dealPipelineIds = null;\n private ?array $dealTypeIds = null;\n\n private ?int $minDealValue = null;\n private ?int $maxDealValue = null;\n\n /**\n * @var string|null\n */\n private $minDealAge;\n\n /**\n * @var string|null\n */\n private $maxDealAge;\n\n /**\n * @var string|null\n */\n private $minPatience;\n\n /**\n * @var string|null\n */\n private $maxPatience;\n\n private ?int $minTalkRatio = null;\n private ?int $maxTalkRatio = null;\n\n /**\n * @var string|null\n */\n private $minMonologue;\n\n /**\n * @var string|null\n */\n private $maxMonologue;\n\n /**\n * @var string|null\n */\n private $minCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $maxCustomerMonologue;\n\n /**\n * @var string|null\n */\n private $minSpeechRate;\n\n /**\n * @var string|null\n */\n private $maxSpeechRate;\n\n private ?float $minScore = null;\n private ?float $maxScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $coachingScores = null;\n\n private ?int $minAutoScore = null;\n private ?int $maxAutoScore = null;\n\n /**\n * @var int[]|null\n */\n private ?array $autoScores = null;\n\n /**\n * @var int[]|null\n */\n private ?array $aiCallScores = null;\n\n /**\n * @var string[]\n */\n private ?array $includeStatuses;\n\n /**\n * @var bool\n */\n private $notLogged;\n\n private bool $hasPendingAiCrmNotes;\n\n private bool $includeHostJoinedMeetings;\n\n private ?int $onlyRecorded;\n\n private ?int $includeInternalConversations = null;\n\n private ?string $searchQuery = null;\n\n /**\n * @var string\n */\n private $transcriptKeywords;\n\n /**\n * @var string\n */\n private $transcriptSaidBy;\n\n /**\n * @var string\n */\n private $transcriptSpeaker;\n\n /**\n * @var string[]|null\n */\n private $languages;\n\n private ?string $externalId = null;\n private ?string $externalIdType = null;\n\n /**\n * @var string|null\n */\n private $minUserQuestions;\n\n /**\n * @var string|null\n */\n private $maxUserQuestions;\n\n /**\n * @var string|null\n */\n private $minEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $maxEngagingQuestions;\n\n /**\n * @var string|null\n */\n private $minInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $maxInsightfulQuestions;\n\n /**\n * @var string|null\n */\n private $minCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $maxCustomerQuestions;\n\n /**\n * @var string|null\n */\n private $sortDirection;\n\n /**\n * @var string|null\n */\n private $sortBy;\n\n private int $limit;\n\n /**\n * @var int\n */\n private $pageNumber;\n\n /**\n * @var bool\n */\n private $empty;\n\n /**\n * @var int\n */\n private $sequenceNumber;\n\n /**\n * @var string\n */\n private $nudgeRunId;\n\n private ?bool $onlyActiveUsers = null;\n\n /**\n * @var string\n */\n private $activityId;\n\n private ?string $context = null;\n\n private ?string $minCommentCount = null;\n private ?string $maxCommentCount = null;\n /** @var array<string, string|string[]> */\n private array $crmFieldValues = [];\n\n public static function createFromRequest(\n array $requestAttributes,\n DateTimeZone $timezone,\n ?string $context = null\n ): self {\n $attributes = Collection::make($requestAttributes);\n\n $instance = new self();\n $instance->context = $context;\n\n $instance->partnerId = $attributes->get('partner_id');\n $instance->teamIds = $attributes->get('team_id');\n $instance->groupIds = $attributes->get('group_id');\n $instance->userIds = $attributes->get('user_id');\n $instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');\n $instance->participantUserIds = $attributes->get('participant_user_id');\n $instance->teamMemberUserIds = $attributes->get('team_member_user_id');\n $instance->excludedUserId = $attributes->get('excluded_user_id');\n $instance->activityTypeIds = $attributes->get('category_id');\n $instance->searchQuery = $attributes->get('query');\n $instance->transcriptKeywords = $attributes->get('transcript_keywords');\n $instance->transcriptSaidBy = $attributes->get('transcript_said_by');\n $instance->transcriptSpeaker = $attributes->get('transcript_speaker');\n $instance->languages = $attributes->get('languages');\n $instance->topicIds = $attributes->get('topic_id', []);\n $instance->compareTopicId = $attributes->get('compare_topic_id');\n $instance->stageIds = $attributes->get('stage_id');\n $instance->currentStageIds = $attributes->get('current_stage_id');\n $instance->playlistIds = $attributes->get('playlist_id');\n $instance->providerId = $attributes->get('provider_id');\n $instance->channelId = $attributes->get('channel_id');\n $instance->channelIds = $attributes->get('channel_ids');\n $hasTranscription = $attributes->get('has_transcription');\n $instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;\n $instance->externalId = $attributes->get('external_id');\n $instance->externalIdType = $attributes->get('external_id_type');\n $instance->notLogged = $attributes->get('not_logged') === '1';\n $instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';\n $instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';\n $instance->onlyRecorded = $attributes->has('only_recorded')\n ? (int) $attributes->get('only_recorded')\n : null;\n $instance->includeInternalConversations = $attributes->has('include_internal_conversations')\n ? (int) $attributes->get('include_internal_conversations')\n : null;\n $instance->minDuration = $attributes->has('min_duration')\n ? (int) $attributes->get('min_duration')\n : null;\n $instance->maxDuration = $attributes->has('max_duration')\n ? (int) $attributes->get('max_duration')\n : null;\n $minDealValue = $attributes->get('min_deal_value');\n $instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;\n $maxDealValue = $attributes->get('max_deal_value');\n $instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;\n $instance->minDealAge = $attributes->get('min_deal_age');\n $instance->maxDealAge = $attributes->get('max_deal_age');\n $instance->minCustomerMonologue = $attributes->get('min_customer_monologue');\n $instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');\n $instance->minMonologue = $attributes->get('min_monologue');\n $instance->maxMonologue = $attributes->get('max_monologue');\n $instance->minTalkRatio = $attributes->has('min_talk_ratio')\n ? (int) $attributes->get('min_talk_ratio')\n : null;\n $instance->maxTalkRatio = $attributes->has('max_talk_ratio')\n ? (int) $attributes->get('max_talk_ratio')\n : null;\n $instance->minPatience = $attributes->get('min_patience');\n $instance->maxPatience = $attributes->get('max_patience');\n $instance->minSpeechRate = $attributes->get('min_speech_rate');\n $instance->maxSpeechRate = $attributes->get('max_speech_rate');\n $instance->minScore = $attributes->has('min_score')\n ? (float) $attributes->get('min_score')\n : null;\n $instance->maxScore = $attributes->has('max_score')\n ? (float) $attributes->get('max_score')\n : null;\n\n $coachingScores = $attributes->get('coaching_score');\n if (is_array($coachingScores)) {\n $instance->coachingScores = array_map('intval', $coachingScores);\n }\n\n $instance->minAutoScore = $attributes->has('min_auto_score')\n ? (int) $attributes->get('min_auto_score')\n : null;\n $instance->maxAutoScore = $attributes->has('max_auto_score')\n ? (int) $attributes->get('max_auto_score')\n : null;\n\n $autoScores = $attributes->get('auto_score');\n if (is_array($autoScores)) {\n $instance->autoScores = array_map('intval', $autoScores);\n }\n\n $aiCallScores = $attributes->get('ai_call_score');\n if (is_array($aiCallScores)) {\n $instance->aiCallScores = array_map('intval', $aiCallScores);\n }\n $instance->minUserQuestions = $attributes->get('min_user_questions');\n $instance->maxUserQuestions = $attributes->get('max_user_questions');\n $instance->minEngagingQuestions = $attributes->get('min_engaging_questions');\n $instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');\n $instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');\n $instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');\n $instance->minCustomerQuestions = $attributes->get('min_customer_questions');\n $instance->maxCustomerQuestions = $attributes->get('max_customer_questions');\n $instance->nudgeRunId = $attributes->get('nudge_run_id');\n $instance->activityId = $attributes->get('activity_id');\n $instance->minCommentCount = $attributes->has('min_comment_count')\n ? (string) $attributes->get('min_comment_count')\n : null;\n $instance->maxCommentCount = $attributes->has('max_comment_count')\n ? (string) $attributes->get('max_comment_count')\n : null;\n $instance->onlyActiveUsers = $attributes->has('only_active_users')\n ? $attributes->get('only_active_users') === '1'\n : null;\n\n $instance->crmFieldValues = $attributes\n ->filter(static function ($value, $key): bool {\n return str_starts_with($key, FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX);\n })\n ->mapWithKeys(static function ($value, string $key): array {\n $key = str_replace_first(FilterDefinition\\CrmFieldCollection::CRITERIA_PREFIX, '', $key);\n\n return [\n $key => $value,\n ];\n })\n ->all();\n\n $instance->limit = (int) $attributes->get('limit', 25);\n $instance->pageNumber = (int) $attributes->get('page', 1);\n $instance->empty = $attributes->isEmpty();\n $instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);\n\n $instance->includeStatuses = $attributes->get('status');\n\n $instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);\n $instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);\n\n $dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;\n $dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;\n\n $instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);\n $instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);\n\n if ($attributes->has('deal_pipeline_id')) {\n $instance->dealPipelineIds = $attributes->get('deal_pipeline_id');\n }\n\n if ($attributes->has('deal_stage_id')) {\n $instance->dealStageIds = $attributes->get('deal_stage_id');\n }\n\n if ($attributes->has('deal_type_id')) {\n $instance->dealTypeIds = $attributes->get('deal_type_id');\n }\n\n if ($attributes->has('start_date')) {\n $instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);\n }\n\n if ($attributes->has('end_date')) {\n $instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);\n }\n\n if ($attributes->has('scheduled_from')) {\n $instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);\n }\n\n if ($attributes->has('scheduled_to')) {\n $instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);\n }\n\n if ($attributes->has('updated_from')) {\n $instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);\n }\n\n if ($attributes->has('updated_to')) {\n $instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);\n }\n\n $instance->sortBy = $attributes->get('sort_by');\n $instance->sortDirection = $attributes->get('sort_direction');\n\n return $instance;\n }\n\n public function setContext(?string $context): self\n {\n $this->context = $context;\n\n return $this;\n }\n\n public function getContext(): ?string\n {\n return $this->context;\n }\n\n public function getMinDealValue(): ?int\n {\n return $this->minDealValue;\n }\n\n public function getMaxDealValue(): ?int\n {\n return $this->maxDealValue;\n }\n\n public function hasMinDealAge(): bool\n {\n return $this->minDealAge !== null;\n }\n\n public function getMinDealAge(): int\n {\n return (int) $this->minDealAge;\n }\n\n public function hasMaxDealAge(): bool\n {\n return $this->maxDealAge !== null;\n }\n\n public function getMaxDealAge(): int\n {\n return (int) $this->maxDealAge;\n }\n\n public function hasTranscriptKeywords(): bool\n {\n return $this->transcriptKeywords !== null;\n }\n\n public function getTranscriptKeywords(): string\n {\n return $this->transcriptKeywords;\n }\n\n public function hasTranscriptSaidBy(): bool\n {\n return $this->transcriptSaidBy !== null;\n }\n\n public function getTranscriptSaidBy(): string\n {\n return $this->transcriptSaidBy;\n }\n\n public function hasTranscriptSpeaker(): bool\n {\n return $this->transcriptSpeaker !== null;\n }\n\n public function getTranscriptSpeaker(): string\n {\n return $this->transcriptSpeaker;\n }\n\n public function getLanguages(): ?array\n {\n return $this->languages;\n }\n\n public function getPartnerId(): ?string\n {\n return $this->partnerId;\n }\n\n public function getMinScore(): ?float\n {\n return $this->minScore;\n }\n\n public function getMaxScore(): ?float\n {\n return $this->maxScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getCoachingScores(): ?array\n {\n return $this->coachingScores;\n }\n\n public function getMinAutoScore(): ?int\n {\n return $this->minAutoScore;\n }\n\n public function getMaxAutoScore(): ?int\n {\n return $this->maxAutoScore;\n }\n\n /**\n * @return int[]|null\n */\n public function getAutoScores(): ?array\n {\n return $this->autoScores;\n }\n\n /**\n * @return int[]|null\n */\n public function getAiCallScores(): ?array\n {\n return $this->aiCallScores;\n }\n\n public function hasMinPatience(): bool\n {\n return $this->minPatience !== null;\n }\n\n public function getMinPatience(): float\n {\n return (float) $this->minPatience;\n }\n\n public function hasMaxPatience(): bool\n {\n return $this->maxPatience !== null;\n }\n\n public function getMaxPatience(): float\n {\n return (float) $this->maxPatience;\n }\n\n public function hasMinSpeechRate(): bool\n {\n return $this->minSpeechRate !== null;\n }\n\n public function getMinSpeechRate(): float\n {\n return (float) $this->minSpeechRate;\n }\n\n public function hasMaxSpeechRate(): bool\n {\n return $this->maxSpeechRate !== null;\n }\n\n public function getMaxSpeechRate(): float\n {\n return (float) $this->maxSpeechRate;\n }\n\n public function getMinTalkRatio(): ?int\n {\n return $this->minTalkRatio;\n }\n\n public function getMaxTalkRatio(): ?int\n {\n return $this->maxTalkRatio;\n }\n\n public function hasMinMonologue(): bool\n {\n return $this->minMonologue !== null;\n }\n\n public function getMinMonologue(): float\n {\n return (float) $this->minMonologue;\n }\n\n public function hasMaxMonologue(): bool\n {\n return $this->maxMonologue !== null;\n }\n\n public function getMaxMonologue(): float\n {\n return (float) $this->maxMonologue;\n }\n\n public function hasMinCustomerMonologue(): bool\n {\n return $this->minCustomerMonologue !== null;\n }\n\n public function getMinCustomerMonologue(): float\n {\n return (float) $this->minCustomerMonologue;\n }\n\n public function hasMaxCustomerMonologue(): bool\n {\n return $this->maxCustomerMonologue !== null;\n }\n\n public function getMaxCustomerMonologue(): float\n {\n return (float) $this->maxCustomerMonologue;\n }\n\n public function getDealCloseDateStart(): ?CarbonImmutable\n {\n return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;\n }\n\n public function getDealCloseDateEnd(): ?CarbonImmutable\n {\n return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;\n }\n\n public function getDealCreatedDateStart(): ?CarbonImmutable\n {\n return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;\n }\n\n public function getDealCreatedDateEnd(): ?CarbonImmutable\n {\n return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;\n }\n\n /**\n * Check if deal close date start parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateStart(): bool\n {\n return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal close date end parameter was explicitly set (even if to null)\n */\n public function hasDealCloseDateEnd(): bool\n {\n return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date start parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateStart(): bool\n {\n return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n /**\n * Check if deal created date end parameter was explicitly set (even if to null)\n */\n public function hasDealCreatedDateEnd(): bool\n {\n return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;\n }\n\n public function hasDealStageIds(): bool\n {\n return $this->dealStageIds !== null;\n }\n\n public function getDealStageIds(): array\n {\n return $this->dealStageIds;\n }\n\n public function hasDealPipelineIds(): bool\n {\n return $this->dealPipelineIds !== null;\n }\n\n public function getDealPipelineIds(): array\n {\n return $this->dealPipelineIds;\n }\n\n public function hasDealTypeIds(): bool\n {\n return $this->dealTypeIds !== null;\n }\n\n public function getDealTypeIds(): array\n {\n return $this->dealTypeIds;\n }\n\n public function getExternalId(): ?string\n {\n return $this->externalId;\n }\n\n public function getExternalIdType(): ?string\n {\n return $this->externalIdType;\n }\n\n public function hasNudgeRunId(): bool\n {\n return $this->nudgeRunId !== null;\n }\n\n public function getNudgeRunId(): string\n {\n return $this->nudgeRunId;\n }\n\n public function hasActivityId(): bool\n {\n return $this->activityId !== null;\n }\n\n public function getActivityId(): string\n {\n return $this->activityId;\n }\n\n public function hasPlaylists(): bool\n {\n return $this->playlistIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getPlaylistIds(): array\n {\n return $this->playlistIds;\n }\n\n public function hasActivityTypeIds(): bool\n {\n return $this->activityTypeIds !== null;\n }\n\n public function getActivityTypeIds(): array\n {\n return $this->activityTypeIds;\n }\n\n /**\n * @return string[]\n */\n public function getTopicIds(): array\n {\n return $this->topicIds;\n }\n\n public function getCompareTopicId(): ?string\n {\n return $this->compareTopicId;\n }\n\n public function hasMinDuration(): bool\n {\n return $this->minDuration !== null;\n }\n\n public function getMinDuration(): int\n {\n return $this->minDuration;\n }\n\n public function hasMaxDuration(): bool\n {\n return $this->maxDuration !== null;\n }\n\n public function getMaxDuration(): int\n {\n return $this->maxDuration;\n }\n\n public function hasStageIds(): bool\n {\n return $this->stageIds !== null;\n }\n\n public function getStageIds(): array\n {\n return $this->stageIds;\n }\n\n public function hasCurrentStageIds(): bool\n {\n return $this->currentStageIds !== null;\n }\n\n public function getCurrentStageIds(): array\n {\n return $this->currentStageIds;\n }\n\n public function hasProviderId(): bool\n {\n return $this->providerId !== null;\n }\n\n public function getProviderId(): string\n {\n return $this->providerId;\n }\n\n public function getChannelId(): ?string\n {\n return $this->channelId;\n }\n\n public function getChannelIds(): ?array\n {\n return $this->channelIds;\n }\n\n public function getHasTranscription(): ?bool\n {\n return $this->hasTranscription;\n }\n\n /**\n * @return string[]|null\n */\n public function getGroupIds(): ?array\n {\n return $this->groupIds;\n }\n\n /** @return array<string, string|string[]> */\n public function getCrmFieldValues(): array\n {\n return $this->crmFieldValues;\n }\n\n public function hasCoachingFeedbackCoachUserIds(): bool\n {\n return $this->coachingFeedbackCoachUserId !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamIds(): array\n {\n return $this->teamIds;\n }\n\n public function hasTeamIds(): bool\n {\n return $this->teamIds !== null;\n }\n\n public function hasTeamMemberUserIds(): bool\n {\n return $this->teamMemberUserIds !== null;\n }\n\n /**\n * @return string[]\n */\n public function getTeamMemberUserIds(): array\n {\n return $this->teamMemberUserIds;\n }\n\n /**\n * @return ?string[]\n */\n public function getUserIds(): ?array\n {\n return $this->userIds;\n }\n\n public function getCoachingFeedbackCoachUserId(): array\n {\n return $this->coachingFeedbackCoachUserId;\n }\n\n public function getParticipantUserIds(): ?array\n {\n return $this->participantUserIds;\n }\n\n\n public function hasExcludedUserId(): bool\n {\n return $this->excludedUserId !== null;\n }\n\n public function getExcludedUserId(): string\n {\n return $this->excludedUserId;\n }\n\n public function getStartDate(): ?CarbonImmutable\n {\n return $this->startDate;\n }\n\n public function getEndDate(): ?CarbonImmutable\n {\n return $this->endDate;\n }\n\n public function getScheduledFrom(): ?Carbon\n {\n return $this->scheduledFrom;\n }\n\n public function getScheduledTo(): ?Carbon\n {\n return $this->scheduledTo;\n }\n\n public function hasUpdatedFrom(): bool\n {\n return $this->updatedFrom !== null;\n }\n\n public function getUpdatedFrom(): Carbon\n {\n return $this->updatedFrom;\n }\n\n public function hasUpdatedTo(): bool\n {\n return $this->updatedTo !== null;\n }\n\n public function getUpdatedTo(): Carbon\n {\n return $this->updatedTo;\n }\n\n public function hasActivityStatusValues(): bool\n {\n return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;\n }\n\n /**\n * @return string[]\n */\n public function getActivityStatusValues(): array\n {\n return $this->includeStatuses;\n }\n\n public function onlyNotLoggedActivities(): bool\n {\n return $this->notLogged === true;\n }\n\n public function hasPendingAiCrmNotes(): bool\n {\n return $this->hasPendingAiCrmNotes === true;\n }\n\n public function hasIncludeHostJoinedMeetings(): bool\n {\n return $this->includeHostJoinedMeetings === true;\n }\n\n public function hasOnlyRecordedActivities(): bool\n {\n return $this->onlyRecorded !== null;\n }\n\n public function getOnlyRecordedActivities(): int\n {\n return $this->onlyRecorded;\n }\n\n public function getIncludeInternalConversations(): ?int\n {\n return $this->includeInternalConversations;\n }\n\n public function hasSearchQuery(): bool\n {\n return $this->searchQuery !== null;\n }\n\n public function getPageNumber(): int\n {\n return $this->pageNumber;\n }\n\n public function getSearchQuery(): ?string\n {\n return $this->searchQuery;\n }\n\n public function hasMinUserQuestions(): bool\n {\n return $this->minUserQuestions !== null;\n }\n\n public function getMinUserQuestions(): int\n {\n return (int) $this->minUserQuestions;\n }\n\n public function hasMaxUserQuestions(): bool\n {\n return $this->maxUserQuestions !== null;\n }\n\n public function getMaxUserQuestions(): int\n {\n return (int) $this->maxUserQuestions;\n }\n\n public function hasMinEngagingQuestions(): bool\n {\n return $this->minEngagingQuestions !== null;\n }\n\n public function getMinEngagingQuestions(): int\n {\n return (int) $this->minEngagingQuestions;\n }\n\n public function hasMaxEngagingQuestions(): bool\n {\n return $this->maxEngagingQuestions !== null;\n }\n\n public function getMaxEngagingQuestions(): int\n {\n return (int) $this->maxEngagingQuestions;\n }\n\n public function hasMinInsightfulQuestions(): bool\n {\n return $this->minInsightfulQuestions !== null;\n }\n\n public function getMinInsightfulQuestions(): int\n {\n return (int) $this->minInsightfulQuestions;\n }\n\n public function hasMaxInsightfulQuestions(): bool\n {\n return $this->maxInsightfulQuestions !== null;\n }\n\n public function getMaxInsightfulQuestions(): int\n {\n return (int) $this->maxInsightfulQuestions;\n }\n\n public function hasMinCustomerQuestions(): bool\n {\n return $this->minCustomerQuestions !== null;\n }\n\n public function getMinCustomerQuestions(): int\n {\n return (int) $this->minCustomerQuestions;\n }\n\n public function hasMaxCustomerQuestions(): bool\n {\n return $this->maxCustomerQuestions !== null;\n }\n\n public function getMaxCustomerQuestions(): int\n {\n return (int) $this->maxCustomerQuestions;\n }\n\n public function hasMinCommentCount(): bool\n {\n return $this->minCommentCount !== null;\n }\n\n public function getMinCommentCount(): string\n {\n return $this->minCommentCount;\n }\n\n public function hasMaxCommentCount(): bool\n {\n return $this->maxCommentCount !== null;\n }\n\n public function getMaxCommentCount(): string\n {\n return $this->maxCommentCount;\n }\n\n public function hasSortDirection(): bool\n {\n return $this->sortDirection !== null;\n }\n\n public function getSortDirection(): string\n {\n return $this->sortDirection;\n }\n\n public function hasSortBy(): bool\n {\n return $this->sortBy !== null;\n }\n\n public function getSortBy(): string\n {\n return $this->sortBy;\n }\n\n public function getLimit(): int\n {\n return $this->limit;\n }\n\n public function isEmpty(): bool\n {\n return $this->empty;\n }\n\n public function isFirstRequest(): bool\n {\n return $this->empty || $this->sequenceNumber === 0;\n }\n\n public function getOnlyActiveUsers(): bool\n {\n return $this->onlyActiveUsers === true;\n }\n\n /**\n * Parse a Deal Insights date from request attributes, handling null/empty values gracefully.\n * This method is specifically for Deal Insights date fields to support the \"All time\" filter.\n *\n * @param Collection $attributes\n * @param string $key\n * @param DateTimeZone $timezone\n *\n * @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is\n * missing, null if explicitly null, CarbonImmutable if valid date\n */\n private static function parseDealInsightsDate(\n Collection $attributes,\n string $key,\n DateTimeZone $timezone\n ): CarbonImmutable|string|null {\n if (! $attributes->has($key)) {\n return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)\n }\n\n $value = $attributes->get($key);\n\n if (empty($value) || $value === 'null') {\n return null; // Parameter is explicitly null (should use \"All time\")\n }\n\n return CarbonImmutable::parse($value, $timezone);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"15","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}]...
|
-5891814088344261183
|
-2815057810544783371
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
isFirstRequest
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/1
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
46
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\VO\Repository\OnDemandActivitySearch;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\CreatedPeriodFilter;
class Criteria
{
/**
* Sentinel value to indicate a date parameter was not provided in the request
*/
private const DATE_PARAMETER_NOT_PROVIDED = 'DATE_PARAMETER_NOT_PROVIDED';
/**
* @var string[]
*/
private $playlistIds;
/**
* @var string[]
*/
private array $topicIds = [];
private ?string $compareTopicId = null;
private ?string $partnerId;
/**
* @var string[]
*/
private $activityTypeIds;
/**
* @var string[]
*/
private $stageIds;
/**
* @var string[]
*/
private $currentStageIds;
/**
* @var string[]|null
*/
private ?array $groupIds = null;
/**
* @var string[]|null
*/
private ?array $userIds = null;
/**
* @var string[]|null
*/
private ?array $coachingFeedbackCoachUserId;
/**
* @var string[]|null
*/
private ?array $participantUserIds = null;
/**
* @var string[]|null
*/
private ?array $teamMemberUserIds;
/**
* @var string[]
*/
private $teamIds;
/**
* @var string
*/
private $providerId;
/**
* Channel ID for filtering activities by a single channel.
*/
private ?string $channelId = null;
/**
* Channel IDs for filtering activities by multiple channels.
* When provided, activities matching any of the specified channels will be returned.
*
* @var string[]|null
*/
private ?array $channelIds = null;
/**
* Filter activities based on whether they have a transcription.
* true = only activities with transcription, false = only activities without transcription, null = no filter
*/
private ?bool $hasTranscription = null;
private ?int $minDuration = null;
private ?int $maxDuration = null;
/**
* @var string
*
* @format uuid
*/
private $excludedUserId;
private ?CarbonImmutable $startDate = null;
private ?CarbonImmutable $endDate = null;
/**
* @var Carbon|null
*/
private $scheduledFrom;
private ?Carbon $scheduledTo = null;
/**
* @var Carbon|null
*/
private $updatedFrom;
/**
* @var Carbon|null
*/
private $updatedTo;
private CarbonImmutable|string|null $dealCloseDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCloseDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateStart = self::DATE_PARAMETER_NOT_PROVIDED;
private CarbonImmutable|string|null $dealCreatedDateEnd = self::DATE_PARAMETER_NOT_PROVIDED;
private ?array $dealStageIds = null;
private ?array $dealPipelineIds = null;
private ?array $dealTypeIds = null;
private ?int $minDealValue = null;
private ?int $maxDealValue = null;
/**
* @var string|null
*/
private $minDealAge;
/**
* @var string|null
*/
private $maxDealAge;
/**
* @var string|null
*/
private $minPatience;
/**
* @var string|null
*/
private $maxPatience;
private ?int $minTalkRatio = null;
private ?int $maxTalkRatio = null;
/**
* @var string|null
*/
private $minMonologue;
/**
* @var string|null
*/
private $maxMonologue;
/**
* @var string|null
*/
private $minCustomerMonologue;
/**
* @var string|null
*/
private $maxCustomerMonologue;
/**
* @var string|null
*/
private $minSpeechRate;
/**
* @var string|null
*/
private $maxSpeechRate;
private ?float $minScore = null;
private ?float $maxScore = null;
/**
* @var int[]|null
*/
private ?array $coachingScores = null;
private ?int $minAutoScore = null;
private ?int $maxAutoScore = null;
/**
* @var int[]|null
*/
private ?array $autoScores = null;
/**
* @var int[]|null
*/
private ?array $aiCallScores = null;
/**
* @var string[]
*/
private ?array $includeStatuses;
/**
* @var bool
*/
private $notLogged;
private bool $hasPendingAiCrmNotes;
private bool $includeHostJoinedMeetings;
private ?int $onlyRecorded;
private ?int $includeInternalConversations = null;
private ?string $searchQuery = null;
/**
* @var string
*/
private $transcriptKeywords;
/**
* @var string
*/
private $transcriptSaidBy;
/**
* @var string
*/
private $transcriptSpeaker;
/**
* @var string[]|null
*/
private $languages;
private ?string $externalId = null;
private ?string $externalIdType = null;
/**
* @var string|null
*/
private $minUserQuestions;
/**
* @var string|null
*/
private $maxUserQuestions;
/**
* @var string|null
*/
private $minEngagingQuestions;
/**
* @var string|null
*/
private $maxEngagingQuestions;
/**
* @var string|null
*/
private $minInsightfulQuestions;
/**
* @var string|null
*/
private $maxInsightfulQuestions;
/**
* @var string|null
*/
private $minCustomerQuestions;
/**
* @var string|null
*/
private $maxCustomerQuestions;
/**
* @var string|null
*/
private $sortDirection;
/**
* @var string|null
*/
private $sortBy;
private int $limit;
/**
* @var int
*/
private $pageNumber;
/**
* @var bool
*/
private $empty;
/**
* @var int
*/
private $sequenceNumber;
/**
* @var string
*/
private $nudgeRunId;
private ?bool $onlyActiveUsers = null;
/**
* @var string
*/
private $activityId;
private ?string $context = null;
private ?string $minCommentCount = null;
private ?string $maxCommentCount = null;
/** @var array<string, string|string[]> */
private array $crmFieldValues = [];
public static function createFromRequest(
array $requestAttributes,
DateTimeZone $timezone,
?string $context = null
): self {
$attributes = Collection::make($requestAttributes);
$instance = new self();
$instance->context = $context;
$instance->partnerId = $attributes->get('partner_id');
$instance->teamIds = $attributes->get('team_id');
$instance->groupIds = $attributes->get('group_id');
$instance->userIds = $attributes->get('user_id');
$instance->coachingFeedbackCoachUserId = $attributes->get('coaching_feedback_coach_id');
$instance->participantUserIds = $attributes->get('participant_user_id');
$instance->teamMemberUserIds = $attributes->get('team_member_user_id');
$instance->excludedUserId = $attributes->get('excluded_user_id');
$instance->activityTypeIds = $attributes->get('category_id');
$instance->searchQuery = $attributes->get('query');
$instance->transcriptKeywords = $attributes->get('transcript_keywords');
$instance->transcriptSaidBy = $attributes->get('transcript_said_by');
$instance->transcriptSpeaker = $attributes->get('transcript_speaker');
$instance->languages = $attributes->get('languages');
$instance->topicIds = $attributes->get('topic_id', []);
$instance->compareTopicId = $attributes->get('compare_topic_id');
$instance->stageIds = $attributes->get('stage_id');
$instance->currentStageIds = $attributes->get('current_stage_id');
$instance->playlistIds = $attributes->get('playlist_id');
$instance->providerId = $attributes->get('provider_id');
$instance->channelId = $attributes->get('channel_id');
$instance->channelIds = $attributes->get('channel_ids');
$hasTranscription = $attributes->get('has_transcription');
$instance->hasTranscription = $hasTranscription !== null ? (bool) $hasTranscription : null;
$instance->externalId = $attributes->get('external_id');
$instance->externalIdType = $attributes->get('external_id_type');
$instance->notLogged = $attributes->get('not_logged') === '1';
$instance->hasPendingAiCrmNotes = $attributes->get('has_pending_ai_crm_notes') === '1';
$instance->includeHostJoinedMeetings = $attributes->get('include_host_joined_meetings') === '1';
$instance->onlyRecorded = $attributes->has('only_recorded')
? (int) $attributes->get('only_recorded')
: null;
$instance->includeInternalConversations = $attributes->has('include_internal_conversations')
? (int) $attributes->get('include_internal_conversations')
: null;
$instance->minDuration = $attributes->has('min_duration')
? (int) $attributes->get('min_duration')
: null;
$instance->maxDuration = $attributes->has('max_duration')
? (int) $attributes->get('max_duration')
: null;
$minDealValue = $attributes->get('min_deal_value');
$instance->minDealValue = $minDealValue !== null ? (int) $minDealValue : null;
$maxDealValue = $attributes->get('max_deal_value');
$instance->maxDealValue = $maxDealValue !== null ? (int) $maxDealValue : null;
$instance->minDealAge = $attributes->get('min_deal_age');
$instance->maxDealAge = $attributes->get('max_deal_age');
$instance->minCustomerMonologue = $attributes->get('min_customer_monologue');
$instance->maxCustomerMonologue = $attributes->get('max_customer_monologue');
$instance->minMonologue = $attributes->get('min_monologue');
$instance->maxMonologue = $attributes->get('max_monologue');
$instance->minTalkRatio = $attributes->has('min_talk_ratio')
? (int) $attributes->get('min_talk_ratio')
: null;
$instance->maxTalkRatio = $attributes->has('max_talk_ratio')
? (int) $attributes->get('max_talk_ratio')
: null;
$instance->minPatience = $attributes->get('min_patience');
$instance->maxPatience = $attributes->get('max_patience');
$instance->minSpeechRate = $attributes->get('min_speech_rate');
$instance->maxSpeechRate = $attributes->get('max_speech_rate');
$instance->minScore = $attributes->has('min_score')
? (float) $attributes->get('min_score')
: null;
$instance->maxScore = $attributes->has('max_score')
? (float) $attributes->get('max_score')
: null;
$coachingScores = $attributes->get('coaching_score');
if (is_array($coachingScores)) {
$instance->coachingScores = array_map('intval', $coachingScores);
}
$instance->minAutoScore = $attributes->has('min_auto_score')
? (int) $attributes->get('min_auto_score')
: null;
$instance->maxAutoScore = $attributes->has('max_auto_score')
? (int) $attributes->get('max_auto_score')
: null;
$autoScores = $attributes->get('auto_score');
if (is_array($autoScores)) {
$instance->autoScores = array_map('intval', $autoScores);
}
$aiCallScores = $attributes->get('ai_call_score');
if (is_array($aiCallScores)) {
$instance->aiCallScores = array_map('intval', $aiCallScores);
}
$instance->minUserQuestions = $attributes->get('min_user_questions');
$instance->maxUserQuestions = $attributes->get('max_user_questions');
$instance->minEngagingQuestions = $attributes->get('min_engaging_questions');
$instance->maxEngagingQuestions = $attributes->get('max_engaging_questions');
$instance->minInsightfulQuestions = $attributes->get('min_insightful_questions');
$instance->maxInsightfulQuestions = $attributes->get('max_insightful_questions');
$instance->minCustomerQuestions = $attributes->get('min_customer_questions');
$instance->maxCustomerQuestions = $attributes->get('max_customer_questions');
$instance->nudgeRunId = $attributes->get('nudge_run_id');
$instance->activityId = $attributes->get('activity_id');
$instance->minCommentCount = $attributes->has('min_comment_count')
? (string) $attributes->get('min_comment_count')
: null;
$instance->maxCommentCount = $attributes->has('max_comment_count')
? (string) $attributes->get('max_comment_count')
: null;
$instance->onlyActiveUsers = $attributes->has('only_active_users')
? $attributes->get('only_active_users') === '1'
: null;
$instance->crmFieldValues = $attributes
->filter(static function ($value, $key): bool {
return str_starts_with($key, FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX);
})
->mapWithKeys(static function ($value, string $key): array {
$key = str_replace_first(FilterDefinition\CrmFieldCollection::CRITERIA_PREFIX, '', $key);
return [
$key => $value,
];
})
->all();
$instance->limit = (int) $attributes->get('limit', 25);
$instance->pageNumber = (int) $attributes->get('page', 1);
$instance->empty = $attributes->isEmpty();
$instance->sequenceNumber = (int) $attributes->get('sequence_number', 0);
$instance->includeStatuses = $attributes->get('status');
$instance->dealCloseDateStart = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_START_DATE, $timezone);
$instance->dealCloseDateEnd = self::parseDealInsightsDate($attributes, ClosingPeriodFilter::KEY_END_DATE, $timezone);
$dealCreatedDateStartKey = CreatedPeriodFilter::KEY_START_DATE;
$dealCreatedDateEndKey = CreatedPeriodFilter::KEY_END_DATE;
$instance->dealCreatedDateStart = self::parseDealInsightsDate($attributes, $dealCreatedDateStartKey, $timezone);
$instance->dealCreatedDateEnd = self::parseDealInsightsDate($attributes, $dealCreatedDateEndKey, $timezone);
if ($attributes->has('deal_pipeline_id')) {
$instance->dealPipelineIds = $attributes->get('deal_pipeline_id');
}
if ($attributes->has('deal_stage_id')) {
$instance->dealStageIds = $attributes->get('deal_stage_id');
}
if ($attributes->has('deal_type_id')) {
$instance->dealTypeIds = $attributes->get('deal_type_id');
}
if ($attributes->has('start_date')) {
$instance->startDate = CarbonImmutable::parse($attributes->get('start_date'), $timezone);
}
if ($attributes->has('end_date')) {
$instance->endDate = CarbonImmutable::parse($attributes->get('end_date'), $timezone);
}
if ($attributes->has('scheduled_from')) {
$instance->scheduledFrom = Carbon::parse($attributes->get('scheduled_from'), $timezone);
}
if ($attributes->has('scheduled_to')) {
$instance->scheduledTo = Carbon::parse($attributes->get('scheduled_to'), $timezone);
}
if ($attributes->has('updated_from')) {
$instance->updatedFrom = Carbon::parse($attributes->get('updated_from'), $timezone);
}
if ($attributes->has('updated_to')) {
$instance->updatedTo = Carbon::parse($attributes->get('updated_to'), $timezone);
}
$instance->sortBy = $attributes->get('sort_by');
$instance->sortDirection = $attributes->get('sort_direction');
return $instance;
}
public function setContext(?string $context): self
{
$this->context = $context;
return $this;
}
public function getContext(): ?string
{
return $this->context;
}
public function getMinDealValue(): ?int
{
return $this->minDealValue;
}
public function getMaxDealValue(): ?int
{
return $this->maxDealValue;
}
public function hasMinDealAge(): bool
{
return $this->minDealAge !== null;
}
public function getMinDealAge(): int
{
return (int) $this->minDealAge;
}
public function hasMaxDealAge(): bool
{
return $this->maxDealAge !== null;
}
public function getMaxDealAge(): int
{
return (int) $this->maxDealAge;
}
public function hasTranscriptKeywords(): bool
{
return $this->transcriptKeywords !== null;
}
public function getTranscriptKeywords(): string
{
return $this->transcriptKeywords;
}
public function hasTranscriptSaidBy(): bool
{
return $this->transcriptSaidBy !== null;
}
public function getTranscriptSaidBy(): string
{
return $this->transcriptSaidBy;
}
public function hasTranscriptSpeaker(): bool
{
return $this->transcriptSpeaker !== null;
}
public function getTranscriptSpeaker(): string
{
return $this->transcriptSpeaker;
}
public function getLanguages(): ?array
{
return $this->languages;
}
public function getPartnerId(): ?string
{
return $this->partnerId;
}
public function getMinScore(): ?float
{
return $this->minScore;
}
public function getMaxScore(): ?float
{
return $this->maxScore;
}
/**
* @return int[]|null
*/
public function getCoachingScores(): ?array
{
return $this->coachingScores;
}
public function getMinAutoScore(): ?int
{
return $this->minAutoScore;
}
public function getMaxAutoScore(): ?int
{
return $this->maxAutoScore;
}
/**
* @return int[]|null
*/
public function getAutoScores(): ?array
{
return $this->autoScores;
}
/**
* @return int[]|null
*/
public function getAiCallScores(): ?array
{
return $this->aiCallScores;
}
public function hasMinPatience(): bool
{
return $this->minPatience !== null;
}
public function getMinPatience(): float
{
return (float) $this->minPatience;
}
public function hasMaxPatience(): bool
{
return $this->maxPatience !== null;
}
public function getMaxPatience(): float
{
return (float) $this->maxPatience;
}
public function hasMinSpeechRate(): bool
{
return $this->minSpeechRate !== null;
}
public function getMinSpeechRate(): float
{
return (float) $this->minSpeechRate;
}
public function hasMaxSpeechRate(): bool
{
return $this->maxSpeechRate !== null;
}
public function getMaxSpeechRate(): float
{
return (float) $this->maxSpeechRate;
}
public function getMinTalkRatio(): ?int
{
return $this->minTalkRatio;
}
public function getMaxTalkRatio(): ?int
{
return $this->maxTalkRatio;
}
public function hasMinMonologue(): bool
{
return $this->minMonologue !== null;
}
public function getMinMonologue(): float
{
return (float) $this->minMonologue;
}
public function hasMaxMonologue(): bool
{
return $this->maxMonologue !== null;
}
public function getMaxMonologue(): float
{
return (float) $this->maxMonologue;
}
public function hasMinCustomerMonologue(): bool
{
return $this->minCustomerMonologue !== null;
}
public function getMinCustomerMonologue(): float
{
return (float) $this->minCustomerMonologue;
}
public function hasMaxCustomerMonologue(): bool
{
return $this->maxCustomerMonologue !== null;
}
public function getMaxCustomerMonologue(): float
{
return (float) $this->maxCustomerMonologue;
}
public function getDealCloseDateStart(): ?CarbonImmutable
{
return $this->dealCloseDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateStart;
}
public function getDealCloseDateEnd(): ?CarbonImmutable
{
return $this->dealCloseDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCloseDateEnd;
}
public function getDealCreatedDateStart(): ?CarbonImmutable
{
return $this->dealCreatedDateStart === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateStart;
}
public function getDealCreatedDateEnd(): ?CarbonImmutable
{
return $this->dealCreatedDateEnd === self::DATE_PARAMETER_NOT_PROVIDED ? null : $this->dealCreatedDateEnd;
}
/**
* Check if deal close date start parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateStart(): bool
{
return $this->dealCloseDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal close date end parameter was explicitly set (even if to null)
*/
public function hasDealCloseDateEnd(): bool
{
return $this->dealCloseDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date start parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateStart(): bool
{
return $this->dealCreatedDateStart !== self::DATE_PARAMETER_NOT_PROVIDED;
}
/**
* Check if deal created date end parameter was explicitly set (even if to null)
*/
public function hasDealCreatedDateEnd(): bool
{
return $this->dealCreatedDateEnd !== self::DATE_PARAMETER_NOT_PROVIDED;
}
public function hasDealStageIds(): bool
{
return $this->dealStageIds !== null;
}
public function getDealStageIds(): array
{
return $this->dealStageIds;
}
public function hasDealPipelineIds(): bool
{
return $this->dealPipelineIds !== null;
}
public function getDealPipelineIds(): array
{
return $this->dealPipelineIds;
}
public function hasDealTypeIds(): bool
{
return $this->dealTypeIds !== null;
}
public function getDealTypeIds(): array
{
return $this->dealTypeIds;
}
public function getExternalId(): ?string
{
return $this->externalId;
}
public function getExternalIdType(): ?string
{
return $this->externalIdType;
}
public function hasNudgeRunId(): bool
{
return $this->nudgeRunId !== null;
}
public function getNudgeRunId(): string
{
return $this->nudgeRunId;
}
public function hasActivityId(): bool
{
return $this->activityId !== null;
}
public function getActivityId(): string
{
return $this->activityId;
}
public function hasPlaylists(): bool
{
return $this->playlistIds !== null;
}
/**
* @return string[]
*/
public function getPlaylistIds(): array
{
return $this->playlistIds;
}
public function hasActivityTypeIds(): bool
{
return $this->activityTypeIds !== null;
}
public function getActivityTypeIds(): array
{
return $this->activityTypeIds;
}
/**
* @return string[]
*/
public function getTopicIds(): array
{
return $this->topicIds;
}
public function getCompareTopicId(): ?string
{
return $this->compareTopicId;
}
public function hasMinDuration(): bool
{
return $this->minDuration !== null;
}
public function getMinDuration(): int
{
return $this->minDuration;
}
public function hasMaxDuration(): bool
{
return $this->maxDuration !== null;
}
public function getMaxDuration(): int
{
return $this->maxDuration;
}
public function hasStageIds(): bool
{
return $this->stageIds !== null;
}
public function getStageIds(): array
{
return $this->stageIds;
}
public function hasCurrentStageIds(): bool
{
return $this->currentStageIds !== null;
}
public function getCurrentStageIds(): array
{
return $this->currentStageIds;
}
public function hasProviderId(): bool
{
return $this->providerId !== null;
}
public function getProviderId(): string
{
return $this->providerId;
}
public function getChannelId(): ?string
{
return $this->channelId;
}
public function getChannelIds(): ?array
{
return $this->channelIds;
}
public function getHasTranscription(): ?bool
{
return $this->hasTranscription;
}
/**
* @return string[]|null
*/
public function getGroupIds(): ?array
{
return $this->groupIds;
}
/** @return array<string, string|string[]> */
public function getCrmFieldValues(): array
{
return $this->crmFieldValues;
}
public function hasCoachingFeedbackCoachUserIds(): bool
{
return $this->coachingFeedbackCoachUserId !== null;
}
/**
* @return string[]
*/
public function getTeamIds(): array
{
return $this->teamIds;
}
public function hasTeamIds(): bool
{
return $this->teamIds !== null;
}
public function hasTeamMemberUserIds(): bool
{
return $this->teamMemberUserIds !== null;
}
/**
* @return string[]
*/
public function getTeamMemberUserIds(): array
{
return $this->teamMemberUserIds;
}
/**
* @return ?string[]
*/
public function getUserIds(): ?array
{
return $this->userIds;
}
public function getCoachingFeedbackCoachUserId(): array
{
return $this->coachingFeedbackCoachUserId;
}
public function getParticipantUserIds(): ?array
{
return $this->participantUserIds;
}
public function hasExcludedUserId(): bool
{
return $this->excludedUserId !== null;
}
public function getExcludedUserId(): string
{
return $this->excludedUserId;
}
public function getStartDate(): ?CarbonImmutable
{
return $this->startDate;
}
public function getEndDate(): ?CarbonImmutable
{
return $this->endDate;
}
public function getScheduledFrom(): ?Carbon
{
return $this->scheduledFrom;
}
public function getScheduledTo(): ?Carbon
{
return $this->scheduledTo;
}
public function hasUpdatedFrom(): bool
{
return $this->updatedFrom !== null;
}
public function getUpdatedFrom(): Carbon
{
return $this->updatedFrom;
}
public function hasUpdatedTo(): bool
{
return $this->updatedTo !== null;
}
public function getUpdatedTo(): Carbon
{
return $this->updatedTo;
}
public function hasActivityStatusValues(): bool
{
return is_array($this->includeStatuses) && count($this->includeStatuses) > 0;
}
/**
* @return string[]
*/
public function getActivityStatusValues(): array
{
return $this->includeStatuses;
}
public function onlyNotLoggedActivities(): bool
{
return $this->notLogged === true;
}
public function hasPendingAiCrmNotes(): bool
{
return $this->hasPendingAiCrmNotes === true;
}
public function hasIncludeHostJoinedMeetings(): bool
{
return $this->includeHostJoinedMeetings === true;
}
public function hasOnlyRecordedActivities(): bool
{
return $this->onlyRecorded !== null;
}
public function getOnlyRecordedActivities(): int
{
return $this->onlyRecorded;
}
public function getIncludeInternalConversations(): ?int
{
return $this->includeInternalConversations;
}
public function hasSearchQuery(): bool
{
return $this->searchQuery !== null;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
public function getSearchQuery(): ?string
{
return $this->searchQuery;
}
public function hasMinUserQuestions(): bool
{
return $this->minUserQuestions !== null;
}
public function getMinUserQuestions(): int
{
return (int) $this->minUserQuestions;
}
public function hasMaxUserQuestions(): bool
{
return $this->maxUserQuestions !== null;
}
public function getMaxUserQuestions(): int
{
return (int) $this->maxUserQuestions;
}
public function hasMinEngagingQuestions(): bool
{
return $this->minEngagingQuestions !== null;
}
public function getMinEngagingQuestions(): int
{
return (int) $this->minEngagingQuestions;
}
public function hasMaxEngagingQuestions(): bool
{
return $this->maxEngagingQuestions !== null;
}
public function getMaxEngagingQuestions(): int
{
return (int) $this->maxEngagingQuestions;
}
public function hasMinInsightfulQuestions(): bool
{
return $this->minInsightfulQuestions !== null;
}
public function getMinInsightfulQuestions(): int
{
return (int) $this->minInsightfulQuestions;
}
public function hasMaxInsightfulQuestions(): bool
{
return $this->maxInsightfulQuestions !== null;
}
public function getMaxInsightfulQuestions(): int
{
return (int) $this->maxInsightfulQuestions;
}
public function hasMinCustomerQuestions(): bool
{
return $this->minCustomerQuestions !== null;
}
public function getMinCustomerQuestions(): int
{
return (int) $this->minCustomerQuestions;
}
public function hasMaxCustomerQuestions(): bool
{
return $this->maxCustomerQuestions !== null;
}
public function getMaxCustomerQuestions(): int
{
return (int) $this->maxCustomerQuestions;
}
public function hasMinCommentCount(): bool
{
return $this->minCommentCount !== null;
}
public function getMinCommentCount(): string
{
return $this->minCommentCount;
}
public function hasMaxCommentCount(): bool
{
return $this->maxCommentCount !== null;
}
public function getMaxCommentCount(): string
{
return $this->maxCommentCount;
}
public function hasSortDirection(): bool
{
return $this->sortDirection !== null;
}
public function getSortDirection(): string
{
return $this->sortDirection;
}
public function hasSortBy(): bool
{
return $this->sortBy !== null;
}
public function getSortBy(): string
{
return $this->sortBy;
}
public function getLimit(): int
{
return $this->limit;
}
public function isEmpty(): bool
{
return $this->empty;
}
public function isFirstRequest(): bool
{
return $this->empty || $this->sequenceNumber === 0;
}
public function getOnlyActiveUsers(): bool
{
return $this->onlyActiveUsers === true;
}
/**
* Parse a Deal Insights date from request attributes, handling null/empty values gracefully.
* This method is specifically for Deal Insights date fields to support the "All time" filter.
*
* @param Collection $attributes
* @param string $key
* @param DateTimeZone $timezone
*
* @return CarbonImmutable|null|string Returns self::DATE_PARAMETER_NOT_PROVIDED if parameter is
* missing, null if explicitly null, CarbonImmutable if valid date
*/
private static function parseDealInsightsDate(
Collection $attributes,
string $key,
DateTimeZone $timezone
): CarbonImmutable|string|null {
if (! $attributes->has($key)) {
return self::DATE_PARAMETER_NOT_PROVIDED; // Parameter is missing (should use default period)
}
$value = $attributes->get($key);
if (empty($value) || $value === 'null') {
return null; // Parameter is explicitly null (should use "All time")
}
return CarbonImmutable::parse($value, $timezone);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error...
|
11039
|
|
11044
|
218
|
10
|
2026-04-14T09:09:58.189117+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157798189_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5786 0Today ~...
|
NULL
|
-953457843742614480
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5786 0Today ~...
|
NULL
|
|
11046
|
218
|
11
|
2026-04-14T09:09:59.323541+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157799323_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5886 0Today ~...
|
NULL
|
322528190909836335
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:5886 0Today ~...
|
11044
|
|
11049
|
218
|
12
|
2026-04-14T09:10:05.807082+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157805807_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:0586 0Today ~...
|
NULL
|
-7008301587682485346
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:0586 0Today ~...
|
NULL
|
|
11051
|
218
|
13
|
2026-04-14T09:10:08.221695+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157808221_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:0786 0Today ~...
|
NULL
|
2476611059197220864
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:0786 0Today ~...
|
11049
|
|
11053
|
218
|
14
|
2026-04-14T09:10:15.223029+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157815223_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:1486 0Today ~...
|
NULL
|
-6911604683408781904
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:1486 0Today ~...
|
NULL
|
|
11056
|
218
|
15
|
2026-04-14T09:10:18.864506+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157818864_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:1886 0Today ~...
|
NULL
|
1735169800189092092
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:1886 0Today ~...
|
11053
|
|
11058
|
218
|
16
|
2026-04-14T09:10:21.172014+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157821172_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:2086 0Today ~...
|
NULL
|
3211127390887117580
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:2086 0Today ~...
|
NULL
|
|
11059
|
218
|
17
|
2026-04-14T09:10:30.946986+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157830946_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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}]...
|
-5643224685284439189
|
-3735472194166249888
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
11058
|
|
11061
|
218
|
18
|
2026-04-14T09:10:31.991374+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157831991_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:3186 0Today ~...
|
NULL
|
-2702122784064222696
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:3186 0Today ~...
|
NULL
|
|
11063
|
218
|
19
|
2026-04-14T09:10:45.826793+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157845826_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\ActivitySearch\\Service;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AiCallScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\AutoScoreFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ClosedDealsFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealCloseDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\HasTopicTriggersFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\PlaybackTopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictActivityChannel;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictPublicActivitiesOnly;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictTeam;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserActive;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\Security\\RestrictUserGroupScope;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\TeamInsights\\TopicFilterDefinition;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\Crm\\LayoutRepository;\nuse Jiminny\\Services\\TeamService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\n\nclass ActivitySearch\n{\n private Container $container;\n\n public function __construct(Container $container)\n {\n $this->container = $container;\n }\n\n public function getOnDemandPageFilters(): FilterDefinitionCollection\n {\n return FilterDefinitionCollection::make(\n Collection::make([\n // special case filters\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityFilter::class,\n FilterDefinition\\ExternalId::class,\n FilterDefinition\\NudgeRunId::class,\n FilterDefinition\\ParticipantUserIn::class,\n FilterDefinition\\PartnerFilterDefinition::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n\n // regular filters\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\HasTranscription::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserGroupIn::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n FilterDefinition\\HasPendingAiCrmNotes::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n return $this\n ->getOnDemandPageFilters()\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n /**\n * @return string[]\n */\n public function getArrayFilterKeys(User $consumer): array\n {\n return $this\n ->getOnDemandPageFilters()\n ->withConsumer($consumer)\n ->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])\n ->keys()\n ->filter(static fn (string $key): bool => ! str_contains($key, '.'))\n ->values()\n ->all();\n }\n\n private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection\n {\n $dateRangeFilterClass = $isExport\n ? FilterDefinition\\TeamInsights\\ConversationExport\\DateRangeFilter::class\n : FilterDefinition\\TeamInsights\\DateRangeFilter::class;\n\n return FilterDefinitionCollection::make(\n Collection::make([\n // special cases\n $dateRangeFilterClass,\n FilterDefinition\\TeamInsights\\Exists::class,\n\n // healthy restrictions\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictPublicActivitiesOnly::class,\n RestrictUserActive::class,\n\n // regular filters\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\TeamInsights\\ActivityDurationRange::class,\n FilterDefinition\\ActivityPlaylistIn::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\TeamInsights\\ActivityRecorded::class,\n FilterDefinition\\ActivityType::class,\n FilterDefinition\\CoachingFeedbackAverageScore::class,\n AutoScoreFilter::class,\n AiCallScoreFilter::class,\n FilterDefinition\\CoachingFeedbackCoachUserIn::class,\n FilterDefinition\\CrmFieldCollection::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\CurrentStage::class,\n FilterDefinition\\CustomerMonologueDuration::class,\n FilterDefinition\\CustomerQuestionCount::class,\n FilterDefinition\\DealAge::class,\n DealCloseDate::class,\n FilterDefinition\\DealValue::class,\n FilterDefinition\\EngagingQuestionCount::class,\n FilterDefinition\\ShowInternalExternalActivitiesFilter::class,\n FilterDefinition\\InsightfulQuestionCount::class,\n FilterDefinition\\LanguageFilterDefinition::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\TeamInsights\\UserInFilter::class,\n FilterDefinition\\TeamInsights\\UserGroupInFilter::class,\n FilterDefinition\\PatienceRange::class,\n PlaybackTopicFilterDefinition::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\SpeechRate::class,\n FilterDefinition\\StageAtCallFilterDefinition::class,\n FilterDefinition\\TalkTimeRatio::class,\n FilterDefinition\\TeamMemberUserIn::class,\n FilterDefinition\\TranscriptionComposite::class,\n FilterDefinition\\UserMonologueDuration::class,\n FilterDefinition\\UserQuestionCount::class,\n FilterDefinition\\CommentCountRange::class,\n // Relevant for topics in deals.\n ClosedDealsFilter::class,\n HasTopicTriggersFilterDefinition::class,\n TopicFilterDefinition::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n private function getDealInsightsPageFilters(\n bool $useCreatedDate = false,\n bool $includeDealType = false,\n bool $includePipeline = false,\n ): FilterDefinitionCollection {\n if ($useCreatedDate) {\n $periodFilterClass = FilterDefinition\\DealInsights\\CreatedPeriodFilter::class;\n } else {\n $periodFilterClass = FilterDefinition\\DealInsights\\ClosingPeriodFilter::class;\n }\n\n $filterSet = [\n RestrictTeam::class,\n $periodFilterClass,\n FilterDefinition\\DealInsights\\UserInFilter::class,\n FilterDefinition\\DealInsights\\UserGroupInFilter::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n FilterDefinition\\DealInsights\\DealNameFilter::class,\n ];\n\n if ($includePipeline) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealPipelineInFilter::class;\n }\n\n if ($includeDealType) {\n $filterSet[] = FilterDefinition\\DealInsights\\DealTypeInFilter::class;\n }\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getTeamInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer,\n bool $isExport = false\n ): FilterDefinitionCollection {\n return $this\n ->getTeamInsightsPageFilters($isExport)\n ->withCriteria($criteria)\n ->withConsumer($consumer)\n ->withRestrictions($consumer->getTeam());\n }\n\n public function getDealInsightsPageFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n $includeDealType = $this->shouldIncludeDealType($consumer);\n $includePipeline = $this->shouldIncludePipeline($consumer);\n $useCreatedDate = $this->shouldUseCreatedDate($consumer);\n\n return $this\n ->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n public function getTeamAiAutomationFilterSet(\n Criteria $criteria,\n User $consumer\n ): FilterDefinitionCollection {\n return $this\n ->getTeamAiAutomationPageFilterSet()\n ->withCriteria($criteria)\n ->withConsumer($consumer);\n }\n\n private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection\n {\n $filterSet = [\n RestrictTeam::class,\n FilterDefinition\\DealInsights\\DealStageInFilter::class,\n ];\n\n return FilterDefinitionCollection::make(\n Collection::make($filterSet)\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n }\n\n public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictTeam::class,\n RestrictUserGroupScope::class,\n RestrictActivityChannel::class,\n RestrictUserActive::class,\n FilterDefinition\\Security\\PrivateMeetingsForCurrentUserOnly::class,\n FilterDefinition\\ActivityActualDate::class,\n FilterDefinition\\Customer::class,\n FilterDefinition\\ActivityChannel::class,\n FilterDefinition\\ActivityDurationRange::class,\n FilterDefinition\\ActivityProviderIn::class,\n FilterDefinition\\ActivityRecorded::class,\n FilterDefinition\\ActivityRecordingStopped::class,\n FilterDefinition\\ActivityScheduledDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\LoggedToCrm::class,\n FilterDefinition\\OrganiserUserIn::class,\n FilterDefinition\\OrganiserUserNotIn::class,\n FilterDefinition\\ProviderFilterDefinition::class,\n FilterDefinition\\SortBy::class,\n FilterDefinition\\UserGroupInOptionalFilter::class,\n FilterDefinition\\OnlyActiveUsers::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all(),\n );\n $filterDefinitionCollection->withCriteria($criteria);\n $filterDefinitionCollection->withConsumer($consumer);\n\n return $filterDefinitionCollection;\n }\n\n public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection\n {\n $filterDefinitionCollection = FilterDefinitionCollection::make(\n Collection::make([\n RestrictActivityChannel::class,\n FilterDefinition\\OrganiserTeamIn::class,\n FilterDefinition\\ActivityUpdatedDate::class,\n FilterDefinition\\ActivityStatusIn::class,\n FilterDefinition\\SortBy::class,\n ])\n ->map(fn (string $className): FilterDefinition => $this->container->make($className))\n ->all()\n );\n $filterDefinitionCollection->withCriteria($criteria);\n\n return $filterDefinitionCollection;\n }\n\n private function shouldIncludeDealType(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {\n return false;\n }\n\n $layoutRepository = app(LayoutRepository::class);\n $layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);\n $dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];\n\n if (! in_array($dealTypeField, $layoutFields)) {\n return false;\n }\n\n return true;\n }\n\n private function shouldIncludePipeline(User $user): bool\n {\n $crmConfig = $user->getTeam()->getCrmConfiguration();\n $crmProviderName = $crmConfig->getProviderName();\n\n if (in_array($crmProviderName, [\n Configuration::PROVIDER_SALESFORCE,\n Configuration::PROVIDER_INTEGRATION_APP,\n ])) {\n return false;\n }\n\n return true;\n }\n\n private function shouldUseCreatedDate(User $user): bool\n {\n $teamService = app(TeamService::class);\n\n return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2779735389457398323
|
5227660842679107912
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\ActivitySearch\Service;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\ActivitySearch\FilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\AiCallScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\AutoScoreFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\ClosedDealsFilter;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealCloseDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\HasTopicTriggersFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\PlaybackTopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictActivityChannel;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictPublicActivitiesOnly;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictTeam;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserActive;
use Jiminny\Component\ActivitySearch\FilterDefinition\Security\RestrictUserGroupScope;
use Jiminny\Component\ActivitySearch\FilterDefinition\TeamInsights\TopicFilterDefinition;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\User;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\LayoutRepository;
use Jiminny\Services\TeamService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
class ActivitySearch
{
private Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getOnDemandPageFilters(): FilterDefinitionCollection
{
return FilterDefinitionCollection::make(
Collection::make([
// special case filters
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityFilter::class,
FilterDefinition\ExternalId::class,
FilterDefinition\NudgeRunId::class,
FilterDefinition\ParticipantUserIn::class,
FilterDefinition\PartnerFilterDefinition::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
// regular filters
FilterDefinition\ActivityActualDate::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\Customer::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\HasTranscription::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserGroupIn:[PASSWORD]
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
FilterDefinition\HasPendingAiCrmNotes::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getOnDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
return $this
->getOnDemandPageFilters()
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
/**
* @return string[]
*/
public function getArrayFilterKeys(User $consumer): array
{
return $this
->getOnDemandPageFilters()
->withConsumer($consumer)
->getPropertyTypes([FilterDefinitionCollection::PROPERTY_TYPE_ARRAY])
->keys()
->filter(static fn (string $key): bool => ! str_contains($key, '.'))
->values()
->all();
}
private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollection
{
$dateRangeFilterClass = $isExport
? FilterDefinition\TeamInsights\ConversationExport\DateRangeFilter::class
: FilterDefinition\TeamInsights\DateRangeFilter::class;
return FilterDefinitionCollection::make(
Collection::make([
// special cases
$dateRangeFilterClass,
FilterDefinition\TeamInsights\Exists::class,
// healthy restrictions
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictPublicActivitiesOnly::class,
RestrictUserActive::class,
// regular filters
FilterDefinition\ActivityChannel::class,
FilterDefinition\TeamInsights\ActivityDurationRange::class,
FilterDefinition\ActivityPlaylistIn::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\TeamInsights\ActivityRecorded::class,
FilterDefinition\ActivityType::class,
FilterDefinition\CoachingFeedbackAverageScore::class,
AutoScoreFilter::class,
AiCallScoreFilter::class,
FilterDefinition\CoachingFeedbackCoachUserIn::class,
FilterDefinition\CrmFieldCollection::class,
FilterDefinition\Customer::class,
FilterDefinition\CurrentStage::class,
FilterDefinition\CustomerMonologueDuration::class,
FilterDefinition\CustomerQuestionCount::class,
FilterDefinition\DealAge::class,
DealCloseDate::class,
FilterDefinition\DealValue::class,
FilterDefinition\EngagingQuestionCount::class,
FilterDefinition\ShowInternalExternalActivitiesFilter::class,
FilterDefinition\InsightfulQuestionCount::class,
FilterDefinition\LanguageFilterDefinition::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\TeamInsights\UserInFilter::class,
FilterDefinition\TeamInsights\UserGroupInFilter::class,
FilterDefinition\PatienceRange::class,
PlaybackTopicFilterDefinition::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\SpeechRate::class,
FilterDefinition\StageAtCallFilterDefinition::class,
FilterDefinition\TalkTimeRatio::class,
FilterDefinition\TeamMemberUserIn::class,
FilterDefinition\TranscriptionComposite::class,
FilterDefinition\UserMonologueDuration::class,
FilterDefinition\UserQuestionCount::class,
FilterDefinition\CommentCountRange::class,
// Relevant for topics in deals.
ClosedDealsFilter::class,
HasTopicTriggersFilterDefinition::class,
TopicFilterDefinition::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
private function getDealInsightsPageFilters(
bool $useCreatedDate = false,
bool $includeDealType = false,
bool $includePipeline = false,
): FilterDefinitionCollection {
if ($useCreatedDate) {
$periodFilterClass = FilterDefinition\DealInsights\CreatedPeriodFilter::class;
} else {
$periodFilterClass = FilterDefinition\DealInsights\ClosingPeriodFilter::class;
}
$filterSet = [
RestrictTeam::class,
$periodFilterClass,
FilterDefinition\DealInsights\UserInFilter::class,
FilterDefinition\DealInsights\UserGroupInFilter::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
FilterDefinition\DealInsights\DealNameFilter::class,
];
if ($includePipeline) {
$filterSet[] = FilterDefinition\DealInsights\DealPipelineInFilter::class;
}
if ($includeDealType) {
$filterSet[] = FilterDefinition\DealInsights\DealTypeInFilter::class;
}
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getTeamInsightsPageFilterSet(
Criteria $criteria,
User $consumer,
bool $isExport = false
): FilterDefinitionCollection {
return $this
->getTeamInsightsPageFilters($isExport)
->withCriteria($criteria)
->withConsumer($consumer)
->withRestrictions($consumer->getTeam());
}
public function getDealInsightsPageFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
$includeDealType = $this->shouldIncludeDealType($consumer);
$includePipeline = $this->shouldIncludePipeline($consumer);
$useCreatedDate = $this->shouldUseCreatedDate($consumer);
return $this
->getDealInsightsPageFilters($useCreatedDate, $includeDealType, $includePipeline)
->withCriteria($criteria)
->withConsumer($consumer);
}
public function getTeamAiAutomationFilterSet(
Criteria $criteria,
User $consumer
): FilterDefinitionCollection {
return $this
->getTeamAiAutomationPageFilterSet()
->withCriteria($criteria)
->withConsumer($consumer);
}
private function getTeamAiAutomationPageFilterSet(): FilterDefinitionCollection
{
$filterSet = [
RestrictTeam::class,
FilterDefinition\DealInsights\DealStageInFilter::class,
];
return FilterDefinitionCollection::make(
Collection::make($filterSet)
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
}
public function getHomepageFilterSet(Criteria $criteria, User $consumer): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictTeam::class,
RestrictUserGroupScope::class,
RestrictActivityChannel::class,
RestrictUserActive::class,
FilterDefinition\Security\PrivateMeetingsForCurrentUserOnly::class,
FilterDefinition\ActivityActualDate::class,
FilterDefinition\Customer::class,
FilterDefinition\ActivityChannel::class,
FilterDefinition\ActivityDurationRange::class,
FilterDefinition\ActivityProviderIn::class,
FilterDefinition\ActivityRecorded::class,
FilterDefinition\ActivityRecordingStopped::class,
FilterDefinition\ActivityScheduledDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\LoggedToCrm::class,
FilterDefinition\OrganiserUserIn::class,
FilterDefinition\OrganiserUserNotIn::class,
FilterDefinition\ProviderFilterDefinition::class,
FilterDefinition\SortBy::class,
FilterDefinition\UserGroupInOptionalFilter::class,
FilterDefinition\OnlyActiveUsers::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all(),
);
$filterDefinitionCollection->withCriteria($criteria);
$filterDefinitionCollection->withConsumer($consumer);
return $filterDefinitionCollection;
}
public function getPartnerFilterSet(Criteria $criteria): FilterDefinitionCollection
{
$filterDefinitionCollection = FilterDefinitionCollection::make(
Collection::make([
RestrictActivityChannel::class,
FilterDefinition\OrganiserTeamIn::class,
FilterDefinition\ActivityUpdatedDate::class,
FilterDefinition\ActivityStatusIn::class,
FilterDefinition\SortBy::class,
])
->map(fn (string $className): FilterDefinition => $this->container->make($className))
->all()
);
$filterDefinitionCollection->withCriteria($criteria);
return $filterDefinitionCollection;
}
private function shouldIncludeDealType(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (! array_key_exists($crmProviderName, Field::BUSINESS_TYPE_FIELDS)) {
return false;
}
$layoutRepository = app(LayoutRepository::class);
$layoutFields = $layoutRepository->getDealInsightLayoutFields($crmConfig);
$dealTypeField = Field::BUSINESS_TYPE_FIELDS[$crmProviderName];
if (! in_array($dealTypeField, $layoutFields)) {
return false;
}
return true;
}
private function shouldIncludePipeline(User $user): bool
{
$crmConfig = $user->getTeam()->getCrmConfiguration();
$crmProviderName = $crmConfig->getProviderName();
if (in_array($crmProviderName, [
Configuration::PROVIDER_SALESFORCE,
Configuration::PROVIDER_INTEGRATION_APP,
])) {
return false;
}
return true;
}
private function shouldUseCreatedDate(User $user): bool
{
$teamService = app(TeamService::class);
return ! $teamService->useDealInsightsClosedDateFilter($user->getTeam());
}
}...
|
11061
|
|
11065
|
218
|
20
|
2026-04-14T09:10:50.720535+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157850720_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:5086 0Today ~...
|
NULL
|
-6594779030521601195
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 50 m100% <47Tue 14 Apr 12:10:5086 0Today ~...
|
NULL
|
|
11067
|
218
|
21
|
2026-04-14T09:11:09.756679+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157869756_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:0986 0Today ~...
|
NULL
|
6552325874727630262
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:0986 0Today ~...
|
11065
|
|
11071
|
218
|
22
|
2026-04-14T09:11:23.750116+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157883750_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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}]...
|
-304832669432165688
|
-8852932249833634878
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
+SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:238Today ~...
|
NULL
|
|
11073
|
218
|
23
|
2026-04-14T09:11:29.975613+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157889975_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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":"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\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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}]...
|
-5923402359908557185
|
-5824280404149966624
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
11071
|
|
11075
|
218
|
24
|
2026-04-14T09:11:31.695905+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157891695_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:3186 0Today ~...
|
NULL
|
-5719028937810332094
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:3186 0Today ~...
|
NULL
|
|
11077
|
218
|
25
|
2026-04-14T09:11:33.017782+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157893017_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:328Today ~...
|
NULL
|
-5212007282136729820
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:328Today ~...
|
11075
|
|
11078
|
218
|
26
|
2026-04-14T09:11:34.875546+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157894875_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:348Today ~...
|
NULL
|
7827588738034217264
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:348Today ~...
|
NULL
|
|
11080
|
218
|
27
|
2026-04-14T09:11:35.570073+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157895570_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:358Today ~...
|
NULL
|
-4557880560709036644
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:358Today ~...
|
11078
|
|
11081
|
218
|
28
|
2026-04-14T09:11:37.952612+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157897952_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:378Today ~...
|
NULL
|
-4143006628765907087
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:378Today ~...
|
NULL
|
|
11083
|
218
|
29
|
2026-04-14T09:11:49.545335+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157909545_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:498Today ~...
|
NULL
|
5678057726196692297
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:498Today ~...
|
11081
|
|
11085
|
218
|
30
|
2026-04-14T09:11:52.515202+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157912515_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:528Today ~...
|
NULL
|
-9105399400763585740
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 49 m100% <47Tue 14 Apr 12:11:528Today ~...
|
NULL
|
|
11087
|
218
|
31
|
2026-04-14T09:12:16.523620+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157936523_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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":"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\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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}]...
|
-5923402359908557185
|
-5824280404149966624
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
11085
|
|
11089
|
218
|
32
|
2026-04-14T09:12:17.461616+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157937461_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:178Today ~...
|
NULL
|
-4726643449717258807
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:178Today ~...
|
NULL
|
|
11091
|
218
|
33
|
2026-04-14T09:12:18.471944+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157938471_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:188Today ~...
|
NULL
|
-35121869694036875
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:188Today ~...
|
11089
|
|
11092
|
218
|
34
|
2026-04-14T09:12:28.131122+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157948131_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","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":"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\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"15","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 Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.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":"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}]...
|
-5923402359908557185
|
-5824280404149966624
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
15
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
11096
|
218
|
35
|
2026-04-14T09:12:44.213143+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157964213_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:448Today ~...
|
NULL
|
-2422904566702702822
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:448Today ~...
|
11092
|
|
11098
|
218
|
36
|
2026-04-14T09:12:45.801130+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157965801_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:458Today ~...
|
NULL
|
-7882397680414276470
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:458Today ~...
|
NULL
|
|
11099
|
218
|
37
|
2026-04-14T09:12:48.223365+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157968223_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:488Today ~...
|
NULL
|
-4218251738522687884
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:488Today ~...
|
11098
|
|
11101
|
218
|
38
|
2026-04-14T09:12:50.260785+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157970260_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:5086 0Today ~...
|
NULL
|
1473412073335811132
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:5086 0Today ~...
|
NULL
|
|
11103
|
218
|
39
|
2026-04-14T09:12:54.053383+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157974053_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:5386 0Today ~...
|
NULL
|
-3805738594133334070
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:5386 0Today ~...
|
11101
|
|
11105
|
218
|
40
|
2026-04-14T09:12:55.490789+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157975490_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:558Today ~...
|
NULL
|
-4372198022168371557
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:558Today ~...
|
NULL
|
|
11106
|
218
|
41
|
2026-04-14T09:12:59.452301+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157979452_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
activitySearch
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/2
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"activitySearch","depth":4,"value":"activitySearch","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7248863172241268667
|
-3674580477490923646
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
activitySearch
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/2
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
+SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aalal]Support Daily - in 2h 48 m100% <47Tue 14 Apr 12:12:5986 0Today ~...
|
11105
|
|
11108
|
218
|
42
|
2026-04-14T09:13:03.261451+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157983261_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:038Today ~...
|
NULL
|
-176072184791533724
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:038Today ~...
|
NULL
|
|
11110
|
218
|
43
|
2026-04-14T09:13:17.689058+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157997689_m1.jpg...
|
PhpStorm
|
faVsco.js – OnDemandV2Controller.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
activitySearch
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/2
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"activitySearch","depth":4,"value":"activitySearch","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1/2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"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":"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\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\V2;\n\nuse Exception;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Http\\JsonResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Validation\\ValidationException;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Component\\AskAnything\\HistoryService;\nuse Jiminny\\Component\\AskJiminnyAi\\Exceptions\\AskJiminnyException;\nuse Jiminny\\Component\\AskJiminnyAi\\OnDemandLevel\\Events\\AskAnythingAbortedChatCompleted;\nuse Jiminny\\Component\\Prophet\\ProphetService;\nuse Jiminny\\Component\\ProphetAi\\StreamRequest;\nuse Jiminny\\Events\\EventDispatcher;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Requests\\API\\V2\\OnDemandAskAnythingRequest;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ActiveStreamsRepository;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Repositories\\TeamAiContextRepository;\nuse Jiminny\\Utils\\FilterNormalizer;\nuse Jiminny\\VO;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\HttpFoundation\\Response as ResponseAlias;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass OnDemandV2Controller extends Controller\n{\n use AuthorizesRequests;\n\n private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;\n\n private const array FILTER_KEY_EXCLUDED_PARAMS = [\n 'sequence_number',\n 'page',\n 'per_page',\n 'limit',\n 'offset',\n ];\n\n public function __construct(\n private readonly ActivitySearch $activitySearch,\n private readonly HistoryService $historyService,\n private readonly ProphetService $prophetService,\n private readonly TeamAiContextRepository $teamAiContextRepository,\n private readonly ActiveStreamsRepository $activeStreamsRepository,\n private readonly EventDispatcher $eventDispatcher,\n private readonly LoggerInterface $logger,\n ) {\n }\n\n /**\n * Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled\n */\n private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse\n {\n if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {\n return new JsonResponse([\n 'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',\n ], ResponseAlias::HTTP_FORBIDDEN);\n }\n\n return null;\n }\n\n /**\n * Get top N activity IDs for Ask Jiminny feature based on filters\n *\n * @throws ValidationException\n * @throws ActivityProviderException\n */\n public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n $topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);\n\n try {\n // Always fetch first N (top count) IDs\n $onDemandActivitySearchCriteria = VO\\Repository\\OnDemandActivitySearch\\Criteria::createFromRequest(\n array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),\n $user->getTimezone()\n );\n\n $filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);\n\n $validationRules = $filterSet->getValidationRules()\n ->merge([\n 'exclude' => 'array',\n 'limit' => 'integer|min:1|max:' . $topCount,\n ])\n ->all();\n\n $request->validate($validationRules);\n\n $hasChangedFilters = $this->hasChangedContextFilter($request, $user);\n $activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);\n $this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);\n\n return new JsonResponse([\n 'count' => count($activityIds),\n 'changed_context_filters' => $hasChangedFilters,\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'ids' => [],\n 'error' => 'Failed to fetch activity IDs',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n private function hasChangedContextFilter(Request $request, User $user): bool\n {\n $filterKey = $this->makeFilterKey($request);\n\n $result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);\n\n if (! $result['changed']) {\n return false;\n }\n\n if ($result['matches_previous']) {\n return false;\n }\n\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n // If no history or last event already matches, return false\n if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {\n return false;\n }\n\n // Append event and notify\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_FILTERS_CHANGED_TYPE\n );\n\n return true;\n }\n\n private function makeFilterKey(Request $request): string\n {\n $filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);\n\n try {\n $normalizedFilters = FilterNormalizer::normalizeFilters($filters);\n $json = json_encode(\n $normalizedFilters,\n JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR\n );\n\n return hash('xxh3', $json);\n } catch (\\JsonException $e) {\n $this->logger->error('Failed to encode filters', [\n 'error' => $e->getMessage(),\n 'filters_keys' => array_keys($filters),\n ]);\n\n throw new AskJiminnyException('Failed to create filter key', 0, $e);\n }\n }\n\n\n /**\n * Get Ask Anything conversation history\n */\n public function getAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse($history, ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to fetch Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'history' => [],\n 'error' => 'Failed to fetch history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Delete Ask Anything conversation history\n */\n public function deleteAskAnythingHistory(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);\n\n return new JsonResponse([\n 'message' => 'History deleted successfully',\n ], ResponseAlias::HTTP_OK);\n } catch (Exception $e) {\n $this->logger->error('Failed to delete Ask Anything history', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to delete history',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n /**\n * Ask Anything - submit question and get AI response\n */\n public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $this->logger->info('AskAnything request received', [\n 'user_id' => $user->getId(),\n 'team_id' => $user->getTeamId(),\n 'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),\n 'message_preview' => mb_substr($request->input('message'), 0, 50),\n ]);\n\n if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {\n return $featureCheck;\n }\n\n try {\n $inputData = $request->validated();\n\n $requestData = [\n 'userQuestion' => $inputData['message'],\n 'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),\n 'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),\n ];\n\n $teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());\n if ($teamAiContext?->getPrompt() !== null) {\n $requestData['teamAiContext'] = $teamAiContext->getPrompt();\n }\n\n $this->historyService->appendToUserHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $inputData['message']\n );\n\n $this->historyService->appendToEventHistory(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n type: HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $streamRequest = StreamRequest::onDemandLevel($requestData);\n\n // Track active stream in Redis\n $this->activeStreamsRepository->start(\n $user->getId(),\n $streamRequest->getId(),\n ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()\n );\n\n return $this->prophetService->getStreamedOrAbortedResponse(\n streamRequest: $streamRequest,\n onCompleted: function (\n string $assistantResponse,\n bool $clientAborted,\n bool $hadError,\n ) use ($user, $streamRequest) {\n // Remove in-progress event\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n // Save to history if backend completed successfully (even if client disconnected)\n // Only skip saving if there was an actual error during streaming\n if (! $hadError && ! empty(trim($assistantResponse))) {\n $this->historyService->appendToSystemHistory(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n $assistantResponse,\n );\n }\n\n // Notify frontend if client disconnected (so it can update UI if still on page)\n if ($clientAborted) {\n $this->eventDispatcher->dispatch(\n new AskAnythingAbortedChatCompleted(\n user: $user,\n identifier: HistoryService::ON_DEMAND_SERVICE,\n response: $assistantResponse,\n )\n );\n }\n\n // Remove active stream in Redis\n $this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());\n }\n );\n } catch (Exception $e) {\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n $this->logger->error('Failed to process Ask Anything request', [\n 'error' => $e->getMessage(),\n 'user_id' => $user->getId(),\n 'question' => $request->input('message'),\n ]);\n\n return new JsonResponse([\n 'error' => 'Failed to process your question',\n ], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);\n }\n }\n\n public function stopStream(Request $request): JsonResponse\n {\n $user = $request->user();\n\n $this->activeStreamsRepository->stopByUser($user->getId());\n\n $this->historyService->removeLastEventMessageByType(\n $user,\n HistoryService::ON_DEMAND_SERVICE,\n HistoryService::EVENT_IN_PROGRESS_TYPE\n );\n\n return response()->json(['message' => 'Stream marked as stopped by user']);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-661265807901631934
|
-5802339713239498621
|
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
AskJiminnyReportActivityServiceT…Defaults
Run 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
activitySearch
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/2
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Component\AskAnything\HistoryService;
use Jiminny\Component\AskJiminnyAi\Exceptions\AskJiminnyException;
use Jiminny\Component\AskJiminnyAi\OnDemandLevel\Events\AskAnythingAbortedChatCompleted;
use Jiminny\Component\Prophet\ProphetService;
use Jiminny\Component\ProphetAi\StreamRequest;
use Jiminny\Events\EventDispatcher;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Requests\API\V2\OnDemandAskAnythingRequest;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\User;
use Jiminny\Repositories\ActiveStreamsRepository;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Repositories\TeamAiContextRepository;
use Jiminny\Utils\FilterNormalizer;
use Jiminny\VO;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Symfony\Component\HttpFoundation\StreamedResponse;
class OnDemandV2Controller extends Controller
{
use AuthorizesRequests;
private const int DEFAULT_TOP_ACTIVITIES_COUNT = 100;
private const array FILTER_KEY_EXCLUDED_PARAMS = [
'sequence_number',
'page',
'per_page',
'limit',
'offset',
];
public function __construct(
private readonly ActivitySearch $activitySearch,
private readonly HistoryService $historyService,
private readonly ProphetService $prophetService,
private readonly TeamAiContextRepository $teamAiContextRepository,
private readonly ActiveStreamsRepository $activeStreamsRepository,
private readonly EventDispatcher $eventDispatcher,
private readonly LoggerInterface $logger,
) {
}
/**
* Check if the user's team has the ASK_JIMINNY_ON_ANYTHING feature enabled
*/
private function checkAskJiminnyOnAnythingFeature(User $user): ?JsonResponse
{
if (! $user->team->hasFeature(FeatureEnum::ASK_JIMINNY_ON_ANYTHING)) {
return new JsonResponse([
'message' => 'Feature ASK_JIMINNY_ON_ANYTHING is not enabled for this team',
], ResponseAlias::HTTP_FORBIDDEN);
}
return null;
}
/**
* Get top N activity IDs for Ask Jiminny feature based on filters
*
* @throws ValidationException
* @throws ActivityProviderException
*/
public function getContextForAskAnythingByFilter(Request $request, ElasticActivityRepository $repository): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
$topCount = $request->input('top_count', self::DEFAULT_TOP_ACTIVITIES_COUNT);
try {
// Always fetch first N (top count) IDs
$onDemandActivitySearchCriteria = VO\Repository\OnDemandActivitySearch\Criteria::createFromRequest(
array_merge($request->all(), ['limit' => $topCount, 'page' => 1]),
$user->getTimezone()
);
$filterSet = $this->activitySearch->getOnDemandPageFilterSet($onDemandActivitySearchCriteria, $user);
$validationRules = $filterSet->getValidationRules()
->merge([
'exclude' => 'array',
'limit' => 'integer|min:1|max:' . $topCount,
])
->all();
$request->validate($validationRules);
$hasChangedFilters = $this->hasChangedContextFilter($request, $user);
$activityIds = $repository->onDemandSearchIdsOnly($user, $onDemandActivitySearchCriteria, $filterSet);
$this->historyService->storeContextIds($user, HistoryService::CONTEXT_IDS, $activityIds);
return new JsonResponse([
'count' => count($activityIds),
'changed_context_filters' => $hasChangedFilters,
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch activity IDs for Ask Jiminny', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'ids' => [],
'error' => 'Failed to fetch activity IDs',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function hasChangedContextFilter(Request $request, User $user): bool
{
$filterKey = $this->makeFilterKey($request);
$result = $this->historyService->compareAndSetFilterKeyWithHistory($user, $filterKey);
if (! $result['changed']) {
return false;
}
if ($result['matches_previous']) {
return false;
}
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
// If no history or last event already matches, return false
if (empty($history) || $this->historyService->hasFilteredChangedEventAsLastMessage($history)) {
return false;
}
// Append event and notify
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_FILTERS_CHANGED_TYPE
);
return true;
}
private function makeFilterKey(Request $request): string
{
$filters = $request->except(self::FILTER_KEY_EXCLUDED_PARAMS);
try {
$normalizedFilters = FilterNormalizer::normalizeFilters($filters);
$json = json_encode(
$normalizedFilters,
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
return hash('xxh3', $json);
} catch (\JsonException $e) {
$this->logger->error('Failed to encode filters', [
'error' => $e->getMessage(),
'filters_keys' => array_keys($filters),
]);
throw new AskJiminnyException('Failed to create filter key', 0, $e);
}
}
/**
* Get Ask Anything conversation history
*/
public function getAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$history = $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse($history, ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to fetch Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'history' => [],
'error' => 'Failed to fetch history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete Ask Anything conversation history
*/
public function deleteAskAnythingHistory(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$this->historyService->deleteHistory($user, HistoryService::ON_DEMAND_SERVICE);
return new JsonResponse([
'message' => 'History deleted successfully',
], ResponseAlias::HTTP_OK);
} catch (Exception $e) {
$this->logger->error('Failed to delete Ask Anything history', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse([
'error' => 'Failed to delete history',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Ask Anything - submit question and get AI response
*/
public function askAnything(OnDemandAskAnythingRequest $request): StreamedResponse|JsonResponse
{
/** @var User $user */
$user = $request->user();
$this->logger->info('AskAnything request received', [
'user_id' => $user->getId(),
'team_id' => $user->getTeamId(),
'request_hash' => md5($request->input('message') . $user->getId() . floor(time() / 60)),
'message_preview' => mb_substr($request->input('message'), 0, 50),
]);
if ($featureCheck = $this->checkAskJiminnyOnAnythingFeature($user)) {
return $featureCheck;
}
try {
$inputData = $request->validated();
$requestData = [
'userQuestion' => $inputData['message'],
'callIds' => $this->historyService->getContextIds($user, HistoryService::CONTEXT_IDS),
'history' => $this->historyService->getHistory($user, HistoryService::ON_DEMAND_SERVICE),
];
$teamAiContext = $this->teamAiContextRepository->getByTeamId($user->getTeamId());
if ($teamAiContext?->getPrompt() !== null) {
$requestData['teamAiContext'] = $teamAiContext->getPrompt();
}
$this->historyService->appendToUserHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$inputData['message']
);
$this->historyService->appendToEventHistory(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
type: HistoryService::EVENT_IN_PROGRESS_TYPE
);
$streamRequest = StreamRequest::onDemandLevel($requestData);
// Track active stream in Redis
$this->activeStreamsRepository->start(
$user->getId(),
$streamRequest->getId(),
ttlSeconds: $streamRequest->getConnectTimeout() + $streamRequest->getReadTimeout()
);
return $this->prophetService->getStreamedOrAbortedResponse(
streamRequest: $streamRequest,
onCompleted: function (
string $assistantResponse,
bool $clientAborted,
bool $hadError,
) use ($user, $streamRequest) {
// Remove in-progress event
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
// Save to history if backend completed successfully (even if client disconnected)
// Only skip saving if there was an actual error during streaming
if (! $hadError && ! empty(trim($assistantResponse))) {
$this->historyService->appendToSystemHistory(
$user,
HistoryService::ON_DEMAND_SERVICE,
$assistantResponse,
);
}
// Notify frontend if client disconnected (so it can update UI if still on page)
if ($clientAborted) {
$this->eventDispatcher->dispatch(
new AskAnythingAbortedChatCompleted(
user: $user,
identifier: HistoryService::ON_DEMAND_SERVICE,
response: $assistantResponse,
)
);
}
// Remove active stream in Redis
$this->activeStreamsRepository->stop($user->getId(), $streamRequest->getId());
}
);
} catch (Exception $e) {
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
$this->logger->error('Failed to process Ask Anything request', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'question' => $request->input('message'),
]);
return new JsonResponse([
'error' => 'Failed to process your question',
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function stopStream(Request $request): JsonResponse
{
$user = $request->user();
$this->activeStreamsRepository->stopByUser($user->getId());
$this->historyService->removeLastEventMessageByType(
$user,
HistoryService::ON_DEMAND_SERVICE,
HistoryService::EVENT_IN_PROGRESS_TYPE
);
return response()->json(['message' => 'Stream marked as stopped by user']);
}
}...
|
11108
|
|
11112
|
218
|
44
|
2026-04-14T09:13:20.862550+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776158000862_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:208Today ~...
|
NULL
|
7166949566923817211
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:208Today ~...
|
NULL
|
|
11114
|
218
|
45
|
2026-04-14T09:13:23.717105+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776158003717_m1.jpg...
|
NULL
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:238Today ~...
|
NULL
|
4807155373909480476
|
NULL
|
click
|
ocr
|
NULL
|
+SlackEDHomeDMSActivityFilesLater..•More+FileEditV +SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# 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 jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa> 0ladl= Support Daily • in 2h 47 m100% <47Tue 14 Apr 12:13:238Today ~...
|
11112
|
|
11118
|
218
|
46
|
2026-04-14T09:13:51.706431+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776158031706_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Find in Files
100+ matches in 26+ files
File mask: Find in Files
100+ matches in 26+ files
File mask:
*.php
*.php
Auto
*.php
Filter Search Results
Pin Window
Search History
sequence...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Find in Files","depth":1,"role_description":"text"},{"role":"AXStaticText","text":"100+ matches in 26+ files","depth":1,"role_description":"text"},{"role":"AXCheckBox","text":"File mask:","depth":1,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"*.php","depth":1,"value":"*.php","role_description":"combo box","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"*.php","depth":6,"role_description":"text"},{"role":"AXStaticText","text":"Auto","depth":6,"role_description":"text"},{"role":"AXTextField","text":"*.php","depth":2,"value":"*.php","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":1,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pin Window","depth":1,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":1,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"sequence","depth":2,"value":"sequence","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
4679965429740400895
|
-8952663104527760037
|
click
|
accessibility
|
NULL
|
Find in Files
100+ matches in 26+ files
File mask: Find in Files
100+ matches in 26+ files
File mask:
*.php
*.php
Auto
*.php
Filter Search Results
Pin Window
Search History
sequence...
|
NULL
|