diff --git a/src/LaunchDarkly/Impl/EventFactory.php b/src/LaunchDarkly/Impl/EventFactory.php index 068ca4e5..4a3651f7 100644 --- a/src/LaunchDarkly/Impl/EventFactory.php +++ b/src/LaunchDarkly/Impl/EventFactory.php @@ -39,6 +39,9 @@ public function newEvalEvent($flag, $user, $detail, $default, $prereqOfFlag = nu if (($addExperimentData || $this->_withReasons) && $detail->getReason()) { $e['reason'] = $detail->getReason()->jsonSerialize(); } + if ($user->getAnonymous()) { + $e['contextKind'] = 'anonymousUser'; + } return $e; } @@ -63,6 +66,9 @@ public function newDefaultEvent($flag, $user, $detail) if ($this->_withReasons && $detail->getReason()) { $e['reason'] = $detail->getReason()->jsonSerialize(); } + if ($user->getAnonymous()) { + $e['contextKind'] = 'anonymousUser'; + } return $e; } @@ -80,6 +86,9 @@ public function newUnknownFlagEvent($key, $user, $detail) if ($this->_withReasons && $detail->getReason()) { $e['reason'] = $detail->getReason()->jsonSerialize(); } + if ($user->getAnonymous()) { + $e['contextKind'] = 'anonymousUser'; + } return $e; } @@ -107,9 +116,35 @@ public function newCustomEvent($eventName, $user, $data, $metricValue) if (isset($metricValue)) { $e['metricValue'] = $metricValue; } + if ($user->getAnonymous()) { + $e['contextKind'] = 'anonymousUser'; + } + return $e; + } + + public function newAliasEvent($user, $previousUser) + { + $e = array( + 'kind' => 'alias', + 'key' => strval($user->getKey()), + 'contextKind' => static::contextKind($user), + 'previousKey' => strval($previousUser->getKey()), + 'previousContextKind' => static::contextKind($previousUser), + 'creationDate' => Util::currentTimeUnixMillis() + ); + return $e; } + private static function contextKind($user) + { + if ($user->getAnonymous()) { + return 'anonymousUser'; + } else { + return 'user'; + } + } + private static function isExperiment($flag, $reason) { if ($reason) { diff --git a/src/LaunchDarkly/LDClient.php b/src/LaunchDarkly/LDClient.php index 5e338d31..f0a6cace 100644 --- a/src/LaunchDarkly/LDClient.php +++ b/src/LaunchDarkly/LDClient.php @@ -375,7 +375,7 @@ public function allFlags($user) public function allFlagsState($user, $options = array()) { if (is_null($user) || is_null($user->getKey())) { - $this->_logger->warn("allFlagsState called with null user or null/empty user key! Returning empty state"); + $this->_logger->warning("allFlagsState called with null user or null/empty user key! Returning empty state"); return new FeatureFlagsState(false); } if ($this->isOffline()) { @@ -408,6 +408,34 @@ public function allFlagsState($user, $options = array()) return $state; } + /** + * Associates two users for analytics purposes. + * + * This can be helpful in the situation where a person is represented by multiple + * LaunchDarkly users. This may happen, for example, when a person initially logs into + * an application-- the person might be represented by an anonymous user prior to logging + * in and a different user after logging in, as denoted by a different user key. + * + * @param LDUser $user the newly identified user. + * @param LDUser $previousUser the previously identified user. + * @return void + */ + public function alias($user, $previousUser) + { + if (is_null($user) || is_null($user->getKey())) { + $this->_logger->warning("Alias called with null user or null/empty user!"); + return; + } + + if (is_null($previousUser) || is_null($previousUser->getKey())) { + $this->_logger->warning("Alias called with null user or null/empty previousUser!"); + return; + } + + $event = $this->_eventFactoryDefault->newAliasEvent($user, $previousUser); + $this->_eventProcessor->enqueue($event); + } + /** * Generates an HMAC sha256 hash for use in Secure mode. * diff --git a/tests/LDClientTest.php b/tests/LDClientTest.php index 8d522eff..e21e9ded 100644 --- a/tests/LDClientTest.php +++ b/tests/LDClientTest.php @@ -313,6 +313,27 @@ public function testVariationDetailSendsEventForUnknownFlag() $this->assertEquals(array('kind' => 'ERROR', 'errorKind' => 'FLAG_NOT_FOUND'), $event['reason']); } + + public function testVariationWithAnonymousUserSendsEventWithAnonymousContextKind() + { + $ep = new MockEventProcessor(); + $client = $this->makeClient(array('event_processor' => $ep)); + + $flag = $this->makeOffFlagWithValue('feature', 'value'); + + $anon_builder = new LDUserBuilder("anon@email.com"); + $anon = $anon_builder->anonymous(true)->build(); + + $client->variation('feature', $anon, 'default'); + + $queue = $ep->getEvents(); + $this->assertEquals(1, sizeof($queue)); + + $event = $queue[0]; + + $this->assertEquals('anonymousUser', $event['contextKind']); + } + public function testAllFlagsReturnsFlagValues() { $flagJson = array( @@ -593,6 +614,46 @@ public function testTrackSendsEventWithDataAndMetricValue() $this->assertEquals($metricValue, $event['metricValue']); } + public function testTrackWithAnonymousUserSendsEventWithAnonymousContextKind() + { + $ep = new MockEventProcessor(); + $client = $this->makeClient(array('event_processor' => $ep)); + + $anon_builder = new LDUserBuilder("anon@email.com"); + $anon = $anon_builder->anonymous(true)->build(); + + $client->track('eventkey', $anon); + $queue = $ep->getEvents(); + $this->assertEquals(1, sizeof($queue)); + $event = $queue[0]; + $this->assertEquals('custom', $event['kind']); + $this->assertEquals('anonymousUser', $event['contextKind']); + } + + public function testAliasEventsAreCorrect() + { + $ep = new MockEventProcessor(); + $client = $this->makeClient(array('event_processor' => $ep)); + + $user_builder = new LDUserBuilder("user@email.com"); + $user = $user_builder->anonymous(false)->build(); + $anon_builder = new LDUserBuilder("anon@email.com"); + $anon = $anon_builder->anonymous(true)->build(); + + $client->alias($user, $anon); + + $queue = $ep->getEvents(); + $this->assertEquals(1, sizeof($queue)); + + $event = $queue[0]; + + $this->assertEquals('alias', $event['kind']); + $this->assertEquals($user->getKey(), $event['key']); + $this->assertEquals('user', $event['contextKind']); + $this->assertEquals($anon->getKey(), $event['previousKey']); + $this->assertEquals('anonymousUser', $event['previousContextKind']); + } + public function testEventsAreNotPublishedIfSendEventsIsFalse() { // In order to do this test, we cannot provide a mock object for Event_Processor_,