/** * @param $consoleCommand SyncCommandBase * @return \Closure */ private static function generatePublisher($consoleCommand) { return function ($conversationsList) use($consoleCommand) { // Publish/create tickets // ---------------------- $errorMapping = array(); $consoleCommand->createProgressBar(count($conversationsList)); /* @var $hybridConversation HybridConversation */ foreach ($conversationsList as $hybridConversation) { /* @var $conversation Conversation */ $conversation = $hybridConversation->getConversation(); $grooveTicketNumber = $hybridConversation->getGrooveTicketNumber(); try { $client = $consoleCommand->getHelpScoutClient(); $createConversationResponse = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($client, $conversation) { $client->createConversation($conversation, true); // imported = true to prevent spam! }); } catch (ApiException $e) { $createdBy = $conversation->getCreatedBy()->getEmail() ? $conversation->getCreatedBy()->getEmail() : "user #" . $conversation->getCreatedBy()->getId(); $consoleCommand->error("Failed to upload HelpScout conversation \"" . $conversation->getSubject() . "\" by " . $createdBy . " at " . $conversation->getCreatedAt() . " (Groove ticket #{$grooveTicketNumber}). Message was: \n" . APIHelper::formatApiExceptionArray($e)); if ($e->getErrors()) { foreach ($e->getErrors() as $error) { $errorMessage = 'Error: [' . $error['property'] . '] ' . $error['message'] . ' (value = ' . print_r($error['value'], TRUE) . ") (Groove ticket #{$grooveTicketNumber})"; $errorMapping[$error['message']][] = $errorMessage; $consoleCommand->getProgressBar()->setMessage($errorMessage . str_pad(' ', 20)); } } else { $errorMapping[$e->getMessage()][] = "[" . $conversation->getCreatedAt() . "] " . $conversation->getSubject() . " (Groove ticket #{$grooveTicketNumber})"; } } catch (\CurlException $ce) { $errorMessage = "CurlException encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")"; $consoleCommand->error($errorMessage . ": " . $ce->getMessage()); $errorMapping[$ce->getMessage()][] = $errorMessage; } catch (\ErrorException $errex) { $errorMessage = "Exception encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")"; $consoleCommand->error($errorMessage . ": " . $errex->getMessage()); $errorMapping[$errex->getMessage()][] = $errorMessage; } catch (\Exception $ex) { $errorMessage = "Exception encountered while publishing Groove ticket #{$grooveTicketNumber} \"" . $conversation->getSubject() . "\" (created by " . $conversation->getCreatedBy()->getEmail() . " at " . $conversation->getCreatedAt() . ")"; $consoleCommand->error($errorMessage . ": " . $ex->getMessage()); $errorMapping[$ex->getMessage()][] = $errorMessage; } $consoleCommand->getProgressBar()->advance(); } if ($consoleCommand->getProgressBar()->getMaxSteps() === 0) { $consoleCommand->getProgressBar()->clear(); } else { $consoleCommand->getProgressBar()->finish(); } if (sizeof($errorMapping) > 0) { $filename = 'sync-tickets-' . date('YmdHis'); $contents = APIHelper::convertErrorMappingArrayToCSVArray($errorMapping); APIHelper::exportArrayToCSV($filename, $contents); $consoleCommand->warn("\nEncountered " . count($contents) . " errors, which have been exported to {$filename}.csv (default location: storage/exports)"); } }; }
public function __construct() { parent::__construct(); $this->grooveClient = new GrooveClient(config('services.groove.key')); $this->helpscoutClient = ApiClient::getInstance(); try { $this->helpscoutClient->setKey(config('services.helpscout.key')); } catch (ApiException $e) { $this->error("There was an error creating the HelpScout client. Message was: " . APIHelper::formatApiExceptionArray($e)); return; } self::$rate_limits[GROOVE] = config('services.groove.ratelimit'); self::$rate_limits[HELPSCOUT] = config('services.helpscout.ratelimit'); }
/** * @param $consoleCommand SyncCommandBase * @return \Closure */ private static function generatePublisher($consoleCommand) { return function ($customersList) use($consoleCommand) { // Publish/create customers // ------------------------ $errorMapping = array(); $consoleCommand->createProgressBar(count($customersList)); /* @var $customer \HelpScout\model\Customer */ foreach ($customersList as $customer) { try { $client = $consoleCommand->getHelpScoutClient(); $helpscoutCreateCustomerResponse = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($client, $customer) { $client->createCustomer($customer); }); } catch (ApiException $e) { // $customerEmails = array_map(function($emailEntry) { // /* @var $emailEntry \HelpScout\model\customer\EmailEntry */ // return $emailEntry->getValue(); // }, $customer->getEmails()); // $consoleCommand->error("Failed to upload HelpScout customer (" . implode(',', $customerEmails) . ")" . ". Message was: " . APIHelper::formatApiExceptionArray($e)); foreach ($e->getErrors() as $error) { $errorMapping[$error['message']][] = "[" . $error['property'] . "] " . $error['message'] . ": " . $error['value']; $consoleCommand->getProgressBar()->setMessage('Error: [' . $error['property'] . '] ' . $error['message'] . ' (' . $error['value'] . ')' . str_pad(' ', 20)); } } $consoleCommand->getProgressBar()->advance(); } if ($consoleCommand->getProgressBar()->getMaxSteps() === 0) { $consoleCommand->getProgressBar()->clear(); } else { $consoleCommand->getProgressBar()->finish(); } if (sizeof($errorMapping) > 0) { $filename = 'sync-customers-' . date('YmdHis'); $contents = APIHelper::convertErrorMappingArrayToCSVArray($errorMapping); APIHelper::exportArrayToCSV($filename, $contents); $consoleCommand->warn("\nEncountered " . count($contents) . " errors, which have been exported to {$filename}.csv (default location: storage/exports)"); } }; }
private function performInitialValidation() { $mailboxesService = $this->getGrooveClient()->mailboxes(); $agentsService = $this->getGrooveClient()->agents(); // Validation check: Ensure each mailbox in Groove maps to a HelpScout mailbox $this->info("Validation check: ensuring each mailbox in Groove maps to a HelpScout mailbox"); $grooveMailboxes = $this->makeRateLimitedRequest(GROOVE, function () use($mailboxesService) { return $mailboxesService->mailboxes()['mailboxes']; }); $hasErrors = false; foreach ($grooveMailboxes as $grooveMailbox) { $grooveMailboxName = $grooveMailbox['name']; if (!($helpscoutMailbox = APIHelper::findMatchingMailboxByName($grooveMailboxName))) { $this->error('Missing corresponding HelpScout mailbox named: ' . $grooveMailboxName); $hasErrors = true; } else { $this->info("[ OK ] " . $grooveMailboxName . " mapped to " . $helpscoutMailbox->getEmail() . " (" . $helpscoutMailbox->getId() . ")"); } } // Validation check: Ensure each agent has a corresponding user in HelpScout $this->info("\nValidation check: ensuring each Groove agent maps to a corresponding HelpScout user"); $grooveAgents = $this->makeRateLimitedRequest(GROOVE, function () use($agentsService) { return $agentsService->list()['agents']; }); foreach ($grooveAgents as $grooveAgent) { $grooveAgentEmail = $grooveAgent['email']; if (!($helpscoutUser = APIHelper::findMatchingUserWithEmail($grooveAgentEmail))) { $this->error('Missing corresponding HelpScout user for email: ' . $grooveAgentEmail); $hasErrors = true; } else { $this->info("[ OK ] " . $grooveAgentEmail . " mapped to HelpScout user " . $helpscoutUser->getFullName() . " (" . $helpscoutUser->getId() . ")"); } } if ($hasErrors) { $this->error("\nValidation failed. Please correct the above errors, otherwise we cannot proceed."); exit; } $this->info("\nValidation passed."); }
/** * @param $consoleCommand SyncCommandBase * @return Closure */ private static function generateProcessor($consoleCommand) { return function ($customersList) use($consoleCommand) { $processedCustomers = array(); foreach ($customersList as $grooveCustomer) { // Groove: email, name, about, twitter_username, title, company_name, phone_number, location, website_url, linkedin_username // HelpScout Customer (subset of Person): firstName, lastName, photoUrl, photoType, gender, age, organization, jobTitle, location, createdAt, modifiedAt // HelpScout Person: id, firstName, lastName, email, phone, type (user, customer, team) $grooveCustomerEmail = $grooveCustomer['email']; try { // TODO: create hybrid model that contains both the Groove and Customer objects $customer = new Customer(); // Groove doesn't separate these fields /* @var $fullName string */ $fullName = $grooveCustomer['name']; list($firstName, $lastName) = APIHelper::extractFirstAndLastNameFromFullName($fullName, $consoleCommand); $customer->setFirstName($firstName); $customer->setLastName($lastName); // Organization must be 60 characters or less $customerOrganization = $grooveCustomer['company_name']; if ($customerOrganization && strlen($customerOrganization) > 60) { $consoleCommand->warn("Warning: Customer organization \"{$customerOrganization}\" must be 60 characters or less. Truncating. (email: {$grooveCustomerEmail})"); $customerOrganization = substr($customerOrganization, 0, 60); } $customer->setOrganization($customerOrganization); // Job title must be 60 characters or less $customerJobTitle = $grooveCustomer['title']; if ($customerJobTitle && strlen($customerJobTitle) > 60) { $consoleCommand->warn("Warning: Customer job title \"{$customerJobTitle}\" must be 60 characters or less. Truncating. (email: {$grooveCustomerEmail})"); $customerJobTitle = substr($customerJobTitle, 0, 60); } $customer->setJobTitle($customerJobTitle); $customer->setLocation($grooveCustomer['location']); $customer->setBackground($grooveCustomer['about']); // Groove doesn't have addresses if ($grooveCustomer['phone_number'] != null) { $phonenumber = new PhoneEntry(); $phonenumber->setValue($grooveCustomer['phone_number']); $phonenumber->setLocation("home"); $customer->setPhones(array($phonenumber)); } // Emails: at least one email is required // Groove only supports one email address, which means the email field could contain multiple emails $emailAddresses = array(); $splitEmails = preg_split("/( |;|,)/", $grooveCustomer['email']); // test to make sure all email addresses are valid if (sizeof($splitEmails) == 1) { $emailEntry = new EmailEntry(); $emailEntry->setValue($grooveCustomer['email']); $emailEntry->setLocation("primary"); array_push($emailAddresses, $emailEntry); } else { // Test to make sure every email address is valid $first = true; foreach ($splitEmails as $addressToTest) { if (strlen(trim($addressToTest)) === 0) { continue; } if (!filter_var($addressToTest, FILTER_VALIDATE_EMAIL)) { // breaking up the address resulted in invalid emails; use the original address $emailAddresses = array(); $emailEntry = new EmailEntry(); $emailEntry->setValue($grooveCustomer['email']); $emailEntry->setLocation("primary"); array_push($emailAddresses, $emailEntry); break; } else { $emailEntry = new EmailEntry(); $emailEntry->setValue($addressToTest); if ($first) { $emailEntry->setLocation("primary"); $first = false; } else { $emailEntry->setLocation("other"); } array_push($emailAddresses, $emailEntry); } } } $customer->setEmails($emailAddresses); // Social Profiles (Groove supports Twitter and LinkedIn) $socialProfiles = array(); if ($grooveCustomer['twitter_username'] != null) { $twitter = new SocialProfileEntry(); $twitter->setValue($grooveCustomer['twitter_username']); $twitter->setType("twitter"); $socialProfiles[] = $twitter; } if ($grooveCustomer['linkedin_username'] != null) { $linkedin = new SocialProfileEntry(); $linkedin->setValue($grooveCustomer['linkedin_username']); $linkedin->setType("linkedin"); $socialProfiles[] = $linkedin; } $customer->setSocialProfiles($socialProfiles); // Groove doesn't have chats if ($grooveCustomer['website_url'] != null) { $website = new WebsiteEntry(); $website->setValue($grooveCustomer['website_url']); $customer->setWebsites(array($website)); } $processedCustomers[] = $customer; } catch (ApiException $e) { $grooveCustomerName = $grooveCustomer['name']; $consoleCommand->error("Failed to create HelpScout customer for Groove customer \"{$grooveCustomerName}\" ({$grooveCustomerEmail}). Message was: " . APIHelper::formatApiExceptionArray($e)); } } return $processedCustomers; }; }
/** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); APIHelper::setConsoleCommand($this); date_default_timezone_set('America/Toronto'); }
/** * @param $consoleCommand SyncCommandBase * @return \Closure */ private static function generateProcessor($consoleCommand) { return function ($ticketsList) use($consoleCommand) { $processedTickets = array(); $checkForDuplicates = $consoleCommand->option('checkDuplicates'); foreach ($ticketsList as $grooveTicket) { $customerEmail = null; $grooveTicketNumber = $grooveTicket['number']; try { if ($checkForDuplicates) { /* @var $searchResults Collection */ $dateString = $grooveTicket['created_at']; $searchResults = $consoleCommand->makeRateLimitedRequest(HELPSCOUT, function () use($consoleCommand, $dateString) { return $consoleCommand->getHelpScoutClient()->conversationSearch("(modifiedAt:[{$dateString} TO {$dateString}])"); }); if ($searchResults->getCount() > 1) { $helpscoutConversationNumber = null; /* @var $searchConversation SearchConversation */ foreach ($searchResults->getItems() as $searchConversation) { if (strcasecmp($searchConversation->getSubject(), $grooveTicket['title']) === 0) { $helpscoutConversationNumber = $searchConversation->getNumber(); break; } } if ($helpscoutConversationNumber) { $consoleCommand->warn("Warning: Duplicate ticket #{$grooveTicketNumber} \"" . $grooveTicket['title'] . "\" on {$dateString} already uploaded to HelpScout (conversation #{$helpscoutConversationNumber}). Skipping."); continue; } } } $hybridConversation = new HybridConversation(); $hybridConversation->setGrooveTicketNumber($grooveTicketNumber); $conversation = new Conversation(); $conversation->setType('email'); $conversation->setSubject($grooveTicket['title']); // mailbox $mailboxName = $grooveTicket['mailbox']; $assignedMailbox = APIHelper::findMatchingMailboxByName($mailboxName); if (!$assignedMailbox) { $mailboxRef = APIHelper::findMatchingMailboxByEmail(config('services.helpscout.default_mailbox'))->toRef(); } else { $mailboxRef = $assignedMailbox->toRef(); } $conversation->setMailbox($mailboxRef); if (!$conversation->getMailbox()) { $exception = new ApiException("Mailbox not found in HelpScout: " . $mailboxName); $exception->setErrors(array(['property' => 'mailbox', 'message' => 'Mailbox not found', 'value' => $mailboxName])); throw $exception; } $tags = $grooveTicket['tags']; if ($tags && count($tags) > 0) { $conversation->setTags($tags); } // CustomerRef $matches = array(); if (isset($grooveTicket['links']['customer']) && preg_match('@^https://api.groovehq.com/v1/customers/(.*)@i', $grooveTicket['links']['customer']['href'], $matches) === 1) { $customerEmail = $matches[1]; if (filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) { $helpscoutPersonRef = new PersonRef((object) array('email' => $customerEmail, 'type' => 'customer')); $conversation->setCustomer($helpscoutPersonRef); $conversation->setCreatedBy($helpscoutPersonRef); } else { $grooveCustomer = $consoleCommand->makeRateLimitedRequest(GROOVE, function () use($consoleCommand, $customerEmail) { return $consoleCommand->getGrooveClient()->customers()->find(['customer_email' => $customerEmail])['customer']; }); $helpscoutPersonRef = new PersonRef((object) array('email' => $grooveCustomer['email'], 'type' => 'customer')); list($firstName, $lastName) = APIHelper::extractFirstAndLastNameFromFullName($grooveCustomer['name'], $consoleCommand); $helpscoutPersonRef->setFirstName($firstName); $helpscoutPersonRef->setLastName($lastName); $conversation->setCustomer($helpscoutPersonRef); $conversation->setCreatedBy($helpscoutPersonRef); } } else { throw new ApiException("No customer defined for ticket: #{$grooveTicketNumber}"); } // CreatedAt $datetime = new DateTime($grooveTicket['created_at']); $conversation->setCreatedAt($datetime->format('c')); $conversation->setThreads(self::retrieveThreadsForGrooveTicket($consoleCommand, $grooveTicket)); $status = APIHelper::getHelpScoutStatusForGrooveState($grooveTicket['state']); if ($status) { $conversation->setStatus($status); } else { $consoleCommand->error("Unknown state provided for Groove ticket #{$grooveTicketNumber}: " . $grooveTicket['state']); } $hybridConversation->setConversation($conversation); $processedTickets[] = $hybridConversation; } catch (ApiException $e) { $consoleCommand->error("Failed to create HelpScout conversation for Groove ticket (#{$grooveTicketNumber} created by {$customerEmail} at " . $grooveTicket['created_at'] . "). Message was: \n" . APIHelper::formatApiExceptionArray($e)); } catch (\CurlException $ce) { $errorMessage = "CurlException encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\""; $consoleCommand->error($errorMessage . ": " . $ce->getMessage()); } catch (\ErrorException $errex) { $errorMessage = "Error encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\""; $consoleCommand->error($errorMessage . ": " . $errex->getMessage()); } catch (\Exception $ex) { $errorMessage = "Exception encountered for ticket #{$grooveTicketNumber} \"" . $grooveTicket['summary'] . "\""; $consoleCommand->error($errorMessage . ": " . $ex->getMessage()); } } return $processedTickets; }; }