Skip to content

Commit e00196b

Browse files
author
tjj5036
committed
Initial commit with partial functionality
0 parents  commit e00196b

File tree

9 files changed

+319
-0
lines changed

9 files changed

+319
-0
lines changed

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2016 Optimizely
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Goptimizely
2+
3+
Optimizely Server Side SDK.... in Go.
4+

examples/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
import (
4+
"github.com/optimizely/go-sdk/optimizely"
5+
)
6+
7+
func main() {
8+
OPTIMIZELY_ACCOUNT_ID := 12345
9+
buffer := optimizely.FetchProjectConfig(OPTIMIZELY_ACCOUNT_ID)
10+
optimizely.DeserializeConfigBuffer(buffer)
11+
}

optimizely/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package optimizely

optimizely/entities.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package optimizely
2+
3+
type AttributeEntity struct {
4+
Id string `json:"id"`
5+
Key string `json:"key"`
6+
Value string `json:"value"`
7+
SegmentId string `json:"segmentId"`
8+
}
9+
10+
type AudienceEntity struct {
11+
Id string `json:"id"`
12+
Name string `json:"name"`
13+
Conditions string `json:"conditions"`
14+
}
15+
16+
type VariationEntity struct {
17+
Id string `json:"id"`
18+
Key string `json:"key"`
19+
EndOfRange int `json:"endOfRange"`
20+
}
21+
22+
type TrafficAllocationEntity struct {
23+
EntityId string `json:"entityId"`
24+
EndOfRange int `json:"endOfRange"`
25+
}
26+
27+
type ExperimentEntity struct {
28+
Id string `json:"id"`
29+
Key string `json:"key"`
30+
Status string `json:"status"`
31+
Variations []VariationEntity `json:"variations"`
32+
PercentageIncluded int `json:"percentageIncluded"`
33+
TrafficAllocation []TrafficAllocationEntity `json:"trafficAllocation"`
34+
AudienceIds []string `json:"audienceIds"`
35+
}
36+
37+
type EventEntity struct {
38+
Id string `json:"id"`
39+
Key string `json:"key"`
40+
ExperimentIds []string `json:"experimentIds"`
41+
}
42+
43+
type DimensionEntity struct {
44+
Id string `json:"id"`
45+
Key string `json:"key"`
46+
SegmentId string `json:"segmentId"`
47+
}
48+
49+
type ProjectConfig struct {
50+
AccountId string `json:"accountId"`
51+
ProjectId string `json:"projectId"`
52+
Revision string `json:"revision"`
53+
Experiments []ExperimentEntity `json:"experiments"`
54+
Events []EventEntity `json:"events"`
55+
Dimensions []DimensionEntity `json:"dimensions"`
56+
Attributes []AttributeEntity `json:"attributes"`
57+
Audiences []AudienceEntity `json:"audiences"`
58+
}

optimizely/event.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package optimizely
2+
3+
var OFFLINE_API_PATH = "https://%v.log.optimizely.com/event" // project_id
4+
var END_USER_ID_TEMPLATE = "oeu-%v" // user_id
5+
var REQUEST_TIMEOUT = 10
6+
7+
const (
8+
ACCOUNT_ID = "d"
9+
PROJECT_ID = "a"
10+
EXPERIMENT = "x"
11+
GOAL_ID = "g"
12+
GOAL_NAME = "n"
13+
SEGMENT = "s"
14+
END_USER_ID = "u"
15+
REVENUE = "v"
16+
SOURCE = "src"
17+
TIME = "time"
18+
)
19+
20+
func DispatchEvent() {
21+
22+
}

optimizely/optimizely.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package optimizely
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
// Track tracks a conversion event for a user_id
9+
// Logs the conversion
10+
// event_key: goal key representing the event which needs to be recorded
11+
// user_id: ID for user.
12+
// attributes: Dict representing visitor attributes and values which need to be recorded.
13+
// event_value: Value associated with the event. Can be used to represent revenue in cents.
14+
func Track(
15+
event_key string,
16+
user_id string,
17+
attributes []AttributeEntity,
18+
event_value string,
19+
project_config ProjectConfig) {
20+
21+
var Url *url.URL
22+
Url, err := url.Parse("http://www.example.com")
23+
if err != nil {
24+
panic("boom")
25+
}
26+
27+
end_user_id := fmt.Sprintf(END_USER_ID_TEMPLATE, user_id)
28+
goal_id := GetGoalIdFromProjectConfig(event_key, project_config)
29+
30+
// build string to make GET request with
31+
parameters := url.Values{}
32+
parameters.Add(ACCOUNT_ID, project_config.AccountId)
33+
parameters.Add(PROJECT_ID, project_config.ProjectId)
34+
parameters.Add(GOAL_NAME, event_key)
35+
parameters.Add(GOAL_ID, goal_id)
36+
parameters.Add(END_USER_ID, end_user_id)
37+
38+
// Set experiment and corresponding variation
39+
BuildExperimentVariationParams(
40+
project_config, event_key, project_config.Experiments, user_id, parameters)
41+
42+
// Set attribute params if any
43+
if len(attributes) > 0 {
44+
BuildAttributeParams(project_config, attributes, parameters)
45+
}
46+
47+
// Set event_value if set and also append the revenue goal ID
48+
if len(event_value) != 0 {
49+
parameters.Add(REVENUE, event_value)
50+
//parameters.Add(GOAL_ID, fmt.Sprintf("{%v},{%v}", goal_id, GetRevenueGoalFromProjectConfig())
51+
}
52+
53+
// Dispatch event
54+
Url.RawQuery = parameters.Encode()
55+
tracking_url := Url.String()
56+
fmt.Print(tracking_url)
57+
58+
}
59+
60+
// Activate buckets visitor and sends impression event to Optimizely
61+
// Activate Logs the impression
62+
// experiment_key: experiment which needs to be activated
63+
// user_id: ID for user
64+
// attributes: optional list representing visitor attributes and values
65+
func Activate(experiment_key string, user_id string, attributes []AttributeEntity) {
66+
67+
}
68+
69+
// GetVariation gets the variation where the visitor will be bucketed
70+
// Experiment_key: experiment which needs to be activated
71+
// User_id: ID for user
72+
func GetVariation(experient_key string, user_id string) {
73+
74+
}

optimizely/project_config.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package optimizely
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
)
10+
11+
var PROJECT_CONFIG_LINK_TEMPLATE = "https://cdn.optimizely.com/json/%v.json"
12+
13+
// FetchConfig retrieves a JSON file from the Optimizely CDN
14+
// and returns it accordingly
15+
func FetchProjectConfig(project_id int) bytes.Buffer {
16+
var b bytes.Buffer
17+
18+
var project_config_url = fmt.Sprintf(PROJECT_CONFIG_LINK_TEMPLATE, project_id)
19+
resp, err := http.Get(project_config_url)
20+
defer resp.Body.Close()
21+
22+
if resp.StatusCode != http.StatusOK {
23+
log.Printf("Status code of %v when fetching project config", resp.StatusCode)
24+
}
25+
if err != nil {
26+
log.Printf("Error fetching project config: %v", err)
27+
return b
28+
}
29+
30+
_, err = b.ReadFrom(resp.Body)
31+
if err != nil {
32+
log.Printf("Error reading JSON response into a buffer")
33+
}
34+
return b
35+
}
36+
37+
// DeserializeConfigBuffer takes a buffer containing a JSON response
38+
// from the Optimizely CDN and converts it to a ProjectConfig entity
39+
// which gets returned
40+
func DeserializeConfigBuffer(b bytes.Buffer) ProjectConfig {
41+
var project_config = ProjectConfig{}
42+
r := bytes.NewReader(b.Bytes())
43+
err := json.NewDecoder(r).Decode(&project_config)
44+
if err != nil {
45+
log.Print(err)
46+
return project_config
47+
}
48+
return project_config
49+
}

optimizely/utils.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package optimizely
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
// GetGoalIdFromProjectConfig returns the goal that matches the event key
9+
// The wording here is a bit confusing
10+
func GetGoalIdFromProjectConfig(event_key string, project_config ProjectConfig) string {
11+
for i := 0; i < len(project_config.Events); i++ {
12+
if project_config.Events[i].Key == event_key {
13+
return project_config.Events[i].Id
14+
}
15+
}
16+
return ""
17+
}
18+
19+
// Get segment Id for the provided attribute key
20+
// project_config: the project_Config
21+
// attributes: the attributes to search through
22+
// attribute_key: the attribute key for which segment ID is to be determined
23+
func GetSementId(project_config ProjectConfig, attributes []AttributeEntity, attribute_key string) string {
24+
for i := 0; i < len(attributes); i++ {
25+
if attributes[i].Key == attribute_key {
26+
return attributes[i].SegmentId
27+
}
28+
}
29+
return ""
30+
}
31+
32+
// BuildAttributeParams adds attribute parameters to the URL Value Map
33+
func BuildAttributeParams(
34+
project_config ProjectConfig,
35+
attributes []AttributeEntity,
36+
parameters url.Values) {
37+
for i := 0; i < len(attributes); i++ {
38+
segment_id := GetSementId(project_config, attributes, attributes[i].Key)
39+
if len(segment_id) > 0 {
40+
parameters.Add(fmt.Sprintf("{%v}{%v}", SEGMENT, segment_id), attributes[i].Value)
41+
}
42+
}
43+
}
44+
45+
// Checks the status of an Experiment to see if its running or not
46+
// This could be a one liner but the `Status` field will most likely
47+
// grow and require a switch or something.
48+
func experiment_is_running(experiment ExperimentEntity) bool {
49+
if experiment.Status != "Running" {
50+
return true
51+
}
52+
return false
53+
}
54+
55+
// Get experiment IDs for the provided goal key
56+
func GetExperimentIdsForGoal(events []EventEntity, goal_key string) []string {
57+
for i := 0; i < len(events); i++ {
58+
if events[i].Key == goal_key {
59+
return events[i].ExperimentIds
60+
}
61+
}
62+
var empty_list []string
63+
return empty_list
64+
}
65+
66+
// BuildExperimentVariationParams maps experiment and corresponding variation as parameters
67+
func BuildExperimentVariationParams(
68+
project_config ProjectConfig,
69+
event_key string,
70+
experiments []ExperimentEntity,
71+
user_id string,
72+
parameters url.Values) {
73+
74+
for i := 0; i < len(experiments); i++ {
75+
if !experiment_is_running(experiments[i]) {
76+
continue
77+
}
78+
experiment_id := experiments[i].Id
79+
experiment_ids := GetExperimentIdsForGoal(project_config.Events, event_key)
80+
for j := 0; j < len(experiment_ids); j++ {
81+
if experiment_ids[j] == experiment_id {
82+
continue
83+
//variation_id := GetVariation(experiments[i].Key)
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)