summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/PRIVACY_POLICY.md51
-rw-r--r--config/config.json3
-rw-r--r--glide.lock14
-rw-r--r--glide.yaml1
-rw-r--r--i18n/en.json8
-rw-r--r--mattermost.go41
-rw-r--r--model/config.go6
-rw-r--r--model/license.go16
-rw-r--r--store/sql_system_store_test.go3
-rw-r--r--store/store.go3
-rw-r--r--utils/config.go1
-rw-r--r--utils/diagnostic.go160
-rw-r--r--vendor/github.com/jehiah/go-strftime/README.md4
-rw-r--r--vendor/github.com/jehiah/go-strftime/strftime.go71
-rw-r--r--vendor/github.com/jehiah/go-strftime/strftime_test.go40
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json17
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/Readme5
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore2
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore22
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md4
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go71
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go40
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS5
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE27
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go204
-rw-r--r--vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go102
-rw-r--r--vendor/github.com/segmentio/analytics-go/History.md67
-rw-r--r--vendor/github.com/segmentio/analytics-go/Makefile10
-rw-r--r--vendor/github.com/segmentio/analytics-go/Readme.md8
-rw-r--r--vendor/github.com/segmentio/analytics-go/analytics.go407
-rw-r--r--vendor/github.com/segmentio/analytics-go/analytics_test.go478
-rw-r--r--vendor/github.com/segmentio/analytics-go/circle.yml18
-rw-r--r--vendor/github.com/segmentio/analytics-go/cli/cli.go174
-rw-r--r--vendor/github.com/segmentio/analytics-go/examples/track.go36
-rw-r--r--vendor/github.com/segmentio/backo-go/README.md80
-rw-r--r--vendor/github.com/segmentio/backo-go/backo.go83
-rw-r--r--vendor/github.com/segmentio/backo-go/backo_test.go77
-rw-r--r--vendor/github.com/xtgo/uuid/AUTHORS5
-rw-r--r--vendor/github.com/xtgo/uuid/LICENSE27
-rw-r--r--vendor/github.com/xtgo/uuid/uuid.go204
-rw-r--r--vendor/github.com/xtgo/uuid/uuid_test.go102
-rw-r--r--webapp/components/admin_console/external_service_settings.jsx21
-rw-r--r--webapp/components/admin_console/log_settings.jsx23
-rw-r--r--webapp/i18n/en.json2
44 files changed, 2703 insertions, 40 deletions
diff --git a/build/PRIVACY_POLICY.md b/build/PRIVACY_POLICY.md
new file mode 100644
index 000000000..cde32caa5
--- /dev/null
+++ b/build/PRIVACY_POLICY.md
@@ -0,0 +1,51 @@
+Mattermost Software Privacy Policy
+
+For the purposes of this Mattermost Software Privacy Policy (the “Privacy Policy”) “you” means you as an individual user, or you as an entity using the software. “Mattermost” or “we” means Mattermost, Inc.
+
+This privacy policy is designed to help you understand which information of yours may be collected by the Mattermost software and how that information is used by Mattermost.
+
+By using the Mattermost software, you consent to the terms of this Privacy Policy.
+
+What information do we collect?
+
+As described below, the kinds of information that Mattermost collects and uses may depend on the configuration options selected by your administrator:
+
+Security Fix Alert Feature:
+
+Mattermost offers a Security Fix Alert feature that is designed to protect your system by attempting to alert you to relevant, high priority security fixes released for your system. You may opt out of receiving Security Alerts by switching off the feature in the [System Console or via the configuration file (see link)](https://docs.mattermost.com/administration/config-settings.html#enable-security-alerts). If you opt out of the Security Fix Alert feature, Mattermost will not collect information required for communication of relevant Security Fix Alerts for your installation and may be unable to contact you regarding such alerts.
+
+When you deploy a Mattermost server with the Security Alert feature enabled, information about your installation that is needed to (i) determine whether your system is at risk of security issues from time-to-time, and (ii) permit notification to you of security fixes and updates to protect your system, will be collected over an encrypted channel and communicated to Mattermost. Such collected and communicated information includes configuration options, the version and type of software components deployed, such as the Mattermost server, database and operating system, activity indicators counting the number of active users and teams, the email addresses of system administrators, and a server identifier. If you are using an activated copy of Mattermost Enterprise Edition, Mattermost may also collect information about available enterprise features including commercial license key registration information.
+
+Error and Diagnostics Reporting Feature:
+
+Mattermost offers an Error and Diagnostics Reporting feature that, when enabled, permits the transmission of information to Mattermost about your system that is needed to diagnose errors and improve the functionality of Mattermost software for deployments with your usage pattern. This information will be collected over an encrypted channel, and include reports of critical errors, configuration options, the version and type of software components deployed, such as Mattermost server, database and operating system, anonymous usage statistics including changes to system preferences and creation of channels and posts. If you are using an activated copy of Mattermost Enterprise Edition, Mattermost may also collect information about usage of enterprise features as well as commercial license key registration information.
+
+You may opt out of the Error and Diagnostics Reporting Feature by switching the feature off in the [System Console user interface or via the system configuration file (see link)]( https://docs.mattermost.com/administration/config-settings.html#enable-error-and-diagnostics-reporting). If you opt out of the Error and Diagnostics Reporting feature, Mattermost will not collect your information required for reporting of errors and diagnostics from your deployment and may be less able to improve system performance for deployments with your usage pattern in future.
+
+Mattermost Hosted Push Notification Service:
+
+If you choose not to compile your own Mattermost push notification service from the source code provided and instead to use the Hosted Push Notification Service (“HPNS”) provided by Mattermost, a privacy policy for HPNS is available here: https://about.mattermost.com/hpns-privacy/
+
+What do we use your information for?
+
+The information we collect from you may be used in the following ways:
+
+To alert you of updates designed to protect your system from newly discovered potential security attacks.
+
+To improve future versions of the Mattermost software for deployments with similar usage patterns as your installation — we continually strive to improve the performance, reliability and functionality of our product, and our efforts to do so are assisted the usage information and feedback we receive from you.
+
+How do we protect your information?
+
+Mattermost takes care to protect the information you provide as part of your use of the Mattermost software from misuse and unauthorized access or disclosure. These steps take into account the sensitivity of the information we collect, process and store, as well as the current state of technology. You may opt-out of the [Security Alerts (see link)](https://docs.mattermost.com/administration/config-settings.html#enable-security-alerts) and [Error and Diagnostics Reporting (see link)](https://docs.mattermost.com/administration/config-settings.html#enable-error-and-diagnostics) services any time from the System Console or via the configuration file, as described previously. Any information that we consider potentially sensitive is transmitted through encrypted channels and we follow generally accepted industry standards to protect the data collected by us, both during transmission and after we receive it.
+
+Internally, we restrict access to all personally identifiable information we receive from You to our personnel that need access to the information in order to do their jobs and that are authorized to handle such information. Our staff is limited and all personnel are committed to adhere to our privacy and security policies. All personnel execute nondisclosure agreements, which provide explicit confidentiality protections. Any staff member who violates our privacy and/or security policies is subject to possible termination of contract and civil/criminal prosecution.
+
+Do we disclose any information to outside parties?
+
+We do not sell, trade, or otherwise transfer the information we collect from you to unaffiliated third parties. We do, however, share the information we collect from you with trusted third parties who assist us in operating our site, conducting our business, or servicing you, provided that those parties agree to keep your information confidential and secure. We also reserve the right to release your information when we believe that release is necessary to comply with the law, enforce our site policies, or protect our own or another’s intellectual property rights, property, or safety.
+
+Changes to our privacy policy
+
+If we decide to change this Privacy Policy, we will include those changes with updated versions of the software.
+
+Last revised September 2016.
diff --git a/config/config.json b/config/config.json
index 9756a356a..1629bada3 100644
--- a/config/config.json
+++ b/config/config.json
@@ -61,7 +61,8 @@
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": "",
- "EnableWebhookDebugging": true
+ "EnableWebhookDebugging": true,
+ "EnableDiagnostics": true
},
"PasswordSettings": {
"MinimumLength": 5,
diff --git a/glide.lock b/glide.lock
index b1bde2847..c1b6daad0 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,5 +1,5 @@
-hash: aa2fadc7f997a93e78d46d009d5695fb079697453d83e6a6dd7481d46ce73b7e
-updated: 2016-05-12T19:14:13.836695608-04:00
+hash: 5edc1c9990dbfc36e041dcdabeab2dd8fdd0c8b8b0540b54d515a0044f63952c
+updated: 2016-09-06T15:41:59.168710473-04:00
imports:
- name: github.com/alecthomas/log4go
version: e5dc62318d9bd58682f1dceb53a4b24e8253682f
@@ -42,6 +42,8 @@ imports:
version: 9c19ed558d5df4da88e2ade9c8940d742aef0e7e
- name: github.com/gorilla/websocket
version: 1f512fc3f05332ba7117626cdfb4e07474e58e60
+- name: github.com/jehiah/go-strftime
+ version: 834e15c05a45371503440cc195bbd05c9a0968d9
- name: github.com/lib/pq
version: ee1442bda7bd1b6a84e913bdb421cb1874ec629d
subpackages:
@@ -70,6 +72,10 @@ imports:
subpackages:
- exif
- tiff
+- name: github.com/segmentio/analytics-go
+ version: bdb0aeca8a993b292b85c9ec17b5ce0ff81848c8
+- name: github.com/segmentio/backo-go
+ version: 204274ad699c0983a70203a566887f17a717fef4
- name: github.com/vaughan0/go-ini
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- name: golang.org/x/crypto
@@ -77,6 +83,8 @@ imports:
subpackages:
- bcrypt
- blowfish
+- name: github.com/xtgo/uuid
+ version: a0b114877d4caeffbd7f87e3757c17fce570fea7
- name: golang.org/x/image
version: f551d3a6b7fc11df315ad9e18b404280680f8bec
subpackages:
@@ -99,4 +107,4 @@ imports:
- store
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
-devImports: []
+devImports: [] \ No newline at end of file
diff --git a/glide.yaml b/glide.yaml
index 81335ef38..c450c6ced 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -39,3 +39,4 @@ import:
- package: gopkg.in/throttled/throttled.v1
subpackages:
- store
+- package: github.com/segmentio/analytics-go
diff --git a/i18n/en.json b/i18n/en.json
index af0500f3e..de69a0b29 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -88,6 +88,10 @@
"translation": "Attempting to recycle the database connection"
},
{
+ "id": "utils.diagnostic.analytics_not_found.app_error",
+ "translation": "Analytics not initialized"
+ },
+ {
"id": "api.admin.remove_certificate.delete.app_error",
"translation": "An error occurred while deleting the certificate. Make sure the file config/{{.Filename}} exists."
},
@@ -4220,6 +4224,10 @@
"translation": "We couldn't count the sessions"
},
{
+ "id": "store.sql_system.get_version.app_error",
+ "translation": "We couldn't get the database version"
+ },
+ {
"id": "store.sql_session.cleanup_expired_sessions.app_error",
"translation": "We encountered an error while deleting expired user sessions"
},
diff --git a/mattermost.go b/mattermost.go
index 391c76096..6d6130604 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -279,6 +279,47 @@ func doSecurityAndDiagnostics() {
}
}
}
+
+ if *utils.Cfg.LogSettings.EnableDiagnostics {
+ utils.SendGeneralDiagnostics()
+ sendServerDiagnostics()
+ }
+}
+
+func sendServerDiagnostics() {
+ var userCount int64
+ var activeUserCount int64
+ var teamCount int64
+
+ if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
+ userCount = ucr.Data.(int64)
+ }
+
+ if ucr := <-api.Srv.Store.Status().GetTotalActiveUsersCount(); ucr.Err == nil {
+ activeUserCount = ucr.Data.(int64)
+ }
+
+ if tcr := <-api.Srv.Store.Team().AnalyticsTeamCount(); tcr.Err == nil {
+ teamCount = tcr.Data.(int64)
+ }
+
+ utils.SendDiagnostic(utils.TRACK_ACTIVITY, map[string]interface{}{
+ "users": userCount,
+ "active_users": activeUserCount,
+ "teams": teamCount,
+ })
+
+ edition := model.BuildEnterpriseReady
+ version := model.CurrentVersion
+ database := utils.Cfg.SqlSettings.DriverName
+ operatingSystem := runtime.GOOS
+
+ utils.SendDiagnostic(utils.TRACK_VERSION, map[string]interface{}{
+ "edition": edition,
+ "version": version,
+ "database": database,
+ "operating_system": operatingSystem,
+ })
}
func runSecurityAndDiagnosticsJob() {
diff --git a/model/config.go b/model/config.go
index b111322a1..3ad1c4c6c 100644
--- a/model/config.go
+++ b/model/config.go
@@ -118,6 +118,7 @@ type LogSettings struct {
FileFormat string
FileLocation string
EnableWebhookDebugging bool
+ EnableDiagnostics *bool
}
type PasswordSettings struct {
@@ -781,6 +782,11 @@ func (o *Config) SetDefaults() {
*o.LocalizationSettings.AvailableLocales = ""
}
+ if o.LogSettings.EnableDiagnostics == nil {
+ o.LogSettings.EnableDiagnostics = new(bool)
+ *o.LogSettings.EnableDiagnostics = true
+ }
+
if o.SamlSettings.Enable == nil {
o.SamlSettings.Enable = new(bool)
*o.SamlSettings.Enable = false
diff --git a/model/license.go b/model/license.go
index 98b88fdeb..1fce1eeb1 100644
--- a/model/license.go
+++ b/model/license.go
@@ -46,6 +46,22 @@ type Features struct {
FutureFeatures *bool `json:"future_features"`
}
+func (f *Features) ToMap() map[string]interface{} {
+ return map[string]interface{}{
+ "ldap": *f.LDAP,
+ "mfa": *f.MFA,
+ "google": *f.GoogleOAuth,
+ "office365": *f.Office365OAuth,
+ "compliance": *f.Compliance,
+ "cluster": *f.Cluster,
+ "custom_brand": *f.CustomBrand,
+ "mhpns": *f.MHPNS,
+ "saml": *f.SAML,
+ "password": *f.PasswordRequirements,
+ "future": *f.FutureFeatures,
+ }
+}
+
func (f *Features) SetDefaults() {
if f.FutureFeatures == nil {
f.FutureFeatures = new(bool)
diff --git a/store/sql_system_store_test.go b/store/sql_system_store_test.go
index 74e2876ad..ab9c4c0f8 100644
--- a/store/sql_system_store_test.go
+++ b/store/sql_system_store_test.go
@@ -4,8 +4,9 @@
package store
import (
- "github.com/mattermost/platform/model"
"testing"
+
+ "github.com/mattermost/platform/model"
)
func TestSqlSystemStore(t *testing.T) {
diff --git a/store/store.go b/store/store.go
index cf08a6c64..620a99ff3 100644
--- a/store/store.go
+++ b/store/store.go
@@ -4,9 +4,10 @@
package store
import (
+ "time"
+
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
- "time"
)
type StoreResult struct {
diff --git a/utils/config.go b/utils/config.go
index 8ef629a9d..2bfb14255 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -241,6 +241,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride)
props["EnableTesting"] = strconv.FormatBool(c.ServiceSettings.EnableTesting)
props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper)
+ props["EnableDiagnostics"] = strconv.FormatBool(*c.LogSettings.EnableDiagnostics)
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications)
diff --git a/utils/diagnostic.go b/utils/diagnostic.go
index 4c73d18f3..d592bac98 100644
--- a/utils/diagnostic.go
+++ b/utils/diagnostic.go
@@ -3,13 +3,11 @@
package utils
-import (
- "net/http"
- "net/url"
-)
+import "github.com/segmentio/analytics-go"
const (
DIAGNOSTIC_URL = "https://d7zmvsa9e04kk.cloudfront.net"
+ SEGMENT_KEY = "ua1qQtmgOZWIM23YjD842tQAsN7Ydi5X"
PROP_DIAGNOSTIC_ID = "id"
PROP_DIAGNOSTIC_CATEGORY = "c"
@@ -22,16 +20,156 @@ const (
PROP_DIAGNOSTIC_TEAM_COUNT = "tc"
PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc"
PROP_DIAGNOSTIC_UNIT_TESTS = "ut"
+
+ TRACK_CONFIG_SERVICE = "service"
+ TRACK_CONFIG_TEAM = "team"
+ TRACK_CONFIG_SQL = "sql"
+ TRACK_CONFIG_LOG = "log"
+ TRACK_CONFIG_FILE = "file"
+ TRACK_CONFIG_RATE = "rate"
+ TRACK_CONFIG_EMAIL = "email"
+ TRACK_CONFIG_PRIVACY = "privacy"
+ TRACK_CONFIG_OAUTH = "oauth"
+ TRACK_CONFIG_LDAP = "ldap"
+ TRACK_CONFIG_COMPLIANCE = "compliance"
+ TRACK_CONFIG_LOCALIZATION = "localization"
+ TRACK_CONFIG_SAML = "saml"
+
+ TRACK_LICENSE = "license"
+ TRACK_ACTIVITY = "activity"
+ TRACK_VERSION = "version"
)
-func SendDiagnostic(values url.Values) {
- if *Cfg.ServiceSettings.EnableSecurityFixAlert {
+var client *analytics.Client
+
+func SendGeneralDiagnostics() {
+ if *Cfg.LogSettings.EnableDiagnostics {
+ initDiagnostics()
+ trackConfig()
+ trackLicense()
+ }
+}
+
+func initDiagnostics() {
+ if client == nil {
+ client = analytics.New(SEGMENT_KEY)
+ client.Identify(&analytics.Identify{
+ UserId: CfgDiagnosticId,
+ })
+ }
+}
+
+func SendDiagnostic(event string, properties map[string]interface{}) {
+ client.Track(&analytics.Track{
+ Event: event,
+ UserId: CfgDiagnosticId,
+ Properties: properties,
+ })
+}
+
+func trackConfig() {
+ SendDiagnostic(TRACK_CONFIG_SERVICE, map[string]interface{}{
+ "web_server_mode": *Cfg.ServiceSettings.WebserverMode,
+ "enable_security_fix_alert": *Cfg.ServiceSettings.EnableSecurityFixAlert,
+ "enable_insecure_outgoing_connections": *Cfg.ServiceSettings.EnableInsecureOutgoingConnections,
+ "enable_incoming_webhooks": Cfg.ServiceSettings.EnableIncomingWebhooks,
+ "enable_outgoing_webhooks": Cfg.ServiceSettings.EnableOutgoingWebhooks,
+ "enable_commands": *Cfg.ServiceSettings.EnableCommands,
+ "enable_only_admin_integrations": *Cfg.ServiceSettings.EnableOnlyAdminIntegrations,
+ "enable_post_username_override": Cfg.ServiceSettings.EnablePostUsernameOverride,
+ "enable_post_icon_override": Cfg.ServiceSettings.EnablePostIconOverride,
+ "enable_custom_emoji": *Cfg.ServiceSettings.EnableCustomEmoji,
+ "restrict_custom_emoji_creation": *Cfg.ServiceSettings.RestrictCustomEmojiCreation,
+ "enable_testing": Cfg.ServiceSettings.EnableTesting,
+ "enable_developer": *Cfg.ServiceSettings.EnableDeveloper,
+ })
- res, err := http.Get(DIAGNOSTIC_URL + "/i?" + values.Encode())
- if err != nil {
- return
- }
+ SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{
+ "enable_user_creation": Cfg.TeamSettings.EnableUserCreation,
+ "enable_team_creation": Cfg.TeamSettings.EnableTeamCreation,
+ "restrict_team_names": *Cfg.TeamSettings.RestrictTeamNames,
+ "restrict_team_invite": *Cfg.TeamSettings.RestrictTeamInvite,
+ "restrict_public_channel_management": *Cfg.TeamSettings.RestrictPublicChannelManagement,
+ "restrict_private_channel_management": *Cfg.TeamSettings.RestrictPrivateChannelManagement,
+ "enable_open_server": *Cfg.TeamSettings.EnableOpenServer,
+ "enable_custom_brand": *Cfg.TeamSettings.EnableCustomBrand,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_SQL, map[string]interface{}{
+ "driver_name": Cfg.SqlSettings.DriverName,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_LOG, map[string]interface{}{
+ "enable_console": Cfg.LogSettings.EnableConsole,
+ "console_level": Cfg.LogSettings.ConsoleLevel,
+ "enable_file": Cfg.LogSettings.EnableFile,
+ "file_level": Cfg.LogSettings.FileLevel,
+ "enable_webhook_debugging": Cfg.LogSettings.EnableWebhookDebugging,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_FILE, map[string]interface{}{
+ "enable_public_links": Cfg.FileSettings.EnablePublicLink,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_RATE, map[string]interface{}{
+ "enable_rate_limiter": Cfg.RateLimitSettings.EnableRateLimiter,
+ "vary_by_remote_address": Cfg.RateLimitSettings.VaryByRemoteAddr,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_EMAIL, map[string]interface{}{
+ "enable_sign_up_with_email": Cfg.EmailSettings.EnableSignUpWithEmail,
+ "enable_sign_in_with_email": *Cfg.EmailSettings.EnableSignInWithEmail,
+ "enable_sign_in_with_username": *Cfg.EmailSettings.EnableSignInWithUsername,
+ "require_email_verification": Cfg.EmailSettings.RequireEmailVerification,
+ "send_email_notifications": Cfg.EmailSettings.SendEmailNotifications,
+ "connection_security": Cfg.EmailSettings.ConnectionSecurity,
+ "send_push_notifications": *Cfg.EmailSettings.SendPushNotifications,
+ "push_notification_contents": *Cfg.EmailSettings.PushNotificationContents,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_PRIVACY, map[string]interface{}{
+ "show_email_address": Cfg.PrivacySettings.ShowEmailAddress,
+ "show_full_name": Cfg.PrivacySettings.ShowFullName,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_OAUTH, map[string]interface{}{
+ "gitlab": Cfg.GitLabSettings.Enable,
+ "google": Cfg.GoogleSettings.Enable,
+ "office365": Cfg.Office365Settings.Enable,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_LDAP, map[string]interface{}{
+ "enable": *Cfg.LdapSettings.Enable,
+ "connection_security": *Cfg.LdapSettings.ConnectionSecurity,
+ "skip_certificate_verification": *Cfg.LdapSettings.SkipCertificateVerification,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_COMPLIANCE, map[string]interface{}{
+ "enable": *Cfg.ComplianceSettings.Enable,
+ "enable_daily": *Cfg.ComplianceSettings.EnableDaily,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_LOCALIZATION, map[string]interface{}{
+ "default_server_locale": *Cfg.LocalizationSettings.DefaultServerLocale,
+ "default_client_locale": *Cfg.LocalizationSettings.DefaultClientLocale,
+ "available_locales": *Cfg.LocalizationSettings.AvailableLocales,
+ })
+
+ SendDiagnostic(TRACK_CONFIG_SAML, map[string]interface{}{
+ "enable": *Cfg.SamlSettings.Enable,
+ })
+}
- res.Body.Close()
+func trackLicense() {
+ if IsLicensed {
+ SendDiagnostic(TRACK_LICENSE, map[string]interface{}{
+ "name": License.Customer.Name,
+ "company": License.Customer.Company,
+ "issued": License.IssuedAt,
+ "start": License.StartsAt,
+ "expire": License.ExpiresAt,
+ "users": *License.Features.Users,
+ "features": License.Features.ToMap(),
+ })
}
}
diff --git a/vendor/github.com/jehiah/go-strftime/README.md b/vendor/github.com/jehiah/go-strftime/README.md
new file mode 100644
index 000000000..8eb240384
--- /dev/null
+++ b/vendor/github.com/jehiah/go-strftime/README.md
@@ -0,0 +1,4 @@
+go-strftime
+===========
+
+go implementation of strftime \ No newline at end of file
diff --git a/vendor/github.com/jehiah/go-strftime/strftime.go b/vendor/github.com/jehiah/go-strftime/strftime.go
new file mode 100644
index 000000000..99e26716f
--- /dev/null
+++ b/vendor/github.com/jehiah/go-strftime/strftime.go
@@ -0,0 +1,71 @@
+// go implementation of strftime
+package strftime
+
+import (
+ "strings"
+ "time"
+)
+
+// taken from time/format.go
+var conversion = map[rune]string {
+ /*stdLongMonth */ 'B':"January",
+ /*stdMonth */ 'b': "Jan",
+ // stdNumMonth */ 'm': "1",
+ /*stdZeroMonth */ 'm': "01",
+ /*stdLongWeekDay */ 'A': "Monday",
+ /*stdWeekDay */ 'a': "Mon",
+ // stdDay */ 'd': "2",
+ // stdUnderDay */ 'd': "_2",
+ /*stdZeroDay */ 'd': "02",
+ /*stdHour */ 'H': "15",
+ // stdHour12 */ 'I': "3",
+ /*stdZeroHour12 */ 'I': "03",
+ // stdMinute */ 'M': "4",
+ /*stdZeroMinute */ 'M': "04",
+ // stdSecond */ 'S': "5",
+ /*stdZeroSecond */ 'S': "05",
+ /*stdLongYear */ 'Y': "2006",
+ /*stdYear */ 'y': "06",
+ /*stdPM */ 'p': "PM",
+ // stdpm */ 'p': "pm",
+ /*stdTZ */ 'Z': "MST",
+ // stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC
+ // stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC
+ /*stdNumTZ */ 'z': "-0700", // always numeric
+ // stdNumShortTZ */ 'b': "-07", // always numeric
+ // stdNumColonTZ */ 'b': "-07:00", // always numeric
+}
+
+// This is an alternative to time.Format because no one knows
+// what date 040305 is supposed to create when used as a 'layout' string
+// this takes standard strftime format options. For a complete list
+// of format options see http://strftime.org/
+func Format(format string, t time.Time) string {
+ retval := make([]byte, 0, len(format))
+ for i, ni := 0, 0; i < len(format); i = ni + 2 {
+ ni = strings.IndexByte(format[i:], '%')
+ if ni < 0 {
+ ni = len(format)
+ } else {
+ ni += i
+ }
+ retval = append(retval, []byte(format[i:ni])...)
+ if ni + 1 < len(format) {
+ c := format[ni + 1]
+ if c == '%' {
+ retval = append(retval, '%')
+ } else {
+ if layoutCmd, ok := conversion[rune(c)]; ok {
+ retval = append(retval, []byte(t.Format(layoutCmd))...)
+ } else {
+ retval = append(retval, '%', c)
+ }
+ }
+ } else {
+ if ni < len(format) {
+ retval = append(retval, '%')
+ }
+ }
+ }
+ return string(retval)
+}
diff --git a/vendor/github.com/jehiah/go-strftime/strftime_test.go b/vendor/github.com/jehiah/go-strftime/strftime_test.go
new file mode 100644
index 000000000..45cbca345
--- /dev/null
+++ b/vendor/github.com/jehiah/go-strftime/strftime_test.go
@@ -0,0 +1,40 @@
+package strftime
+
+import (
+ "time"
+ "fmt"
+ "testing"
+)
+
+func ExampleFormat() {
+ t := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ t = t.In(utc)
+ fmt.Println(Format("%Y-%m-%d %H:%M:%S", t))
+ // Output:
+ // 2012-06-21 02:12:56
+}
+
+func TestNoLeadingPercentSign(t *testing.T) {
+ tm := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ tm = tm.In(utc)
+ result := Format("aaabbb0123456789%Y", tm)
+ if result != "aaabbb01234567892012" {
+ t.Logf("%s != %s", result, "aaabbb01234567892012")
+ t.Fail()
+ }
+}
+
+
+func TestUnsupported(t *testing.T) {
+ tm := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ tm = tm.In(utc)
+ result := Format("%0%1%%%2", tm)
+ if result != "%0%1%%2" {
+ t.Logf("%s != %s", result, "%0%1%%2")
+ t.Fail()
+ }
+}
+
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json b/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json
new file mode 100644
index 000000000..e1b79eb2d
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json
@@ -0,0 +1,17 @@
+{
+ "ImportPath": "github.com/segmentio/analytics-go",
+ "GoVersion": "go1.4.2",
+ "Packages": [
+ "./..."
+ ],
+ "Deps": [
+ {
+ "ImportPath": "github.com/jehiah/go-strftime",
+ "Rev": "834e15c05a45371503440cc195bbd05c9a0968d9"
+ },
+ {
+ "ImportPath": "github.com/xtgo/uuid",
+ "Rev": "a0b114877d4caeffbd7f87e3757c17fce570fea7"
+ }
+ ]
+}
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/Readme b/vendor/github.com/segmentio/analytics-go/Godeps/Readme
new file mode 100644
index 000000000..4cdaa53d5
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore
new file mode 100644
index 000000000..f037d684e
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore
@@ -0,0 +1,2 @@
+/pkg
+/bin
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore
new file mode 100644
index 000000000..00268614f
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore
@@ -0,0 +1,22 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md
new file mode 100644
index 000000000..8eb240384
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md
@@ -0,0 +1,4 @@
+go-strftime
+===========
+
+go implementation of strftime \ No newline at end of file
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go
new file mode 100644
index 000000000..99e26716f
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go
@@ -0,0 +1,71 @@
+// go implementation of strftime
+package strftime
+
+import (
+ "strings"
+ "time"
+)
+
+// taken from time/format.go
+var conversion = map[rune]string {
+ /*stdLongMonth */ 'B':"January",
+ /*stdMonth */ 'b': "Jan",
+ // stdNumMonth */ 'm': "1",
+ /*stdZeroMonth */ 'm': "01",
+ /*stdLongWeekDay */ 'A': "Monday",
+ /*stdWeekDay */ 'a': "Mon",
+ // stdDay */ 'd': "2",
+ // stdUnderDay */ 'd': "_2",
+ /*stdZeroDay */ 'd': "02",
+ /*stdHour */ 'H': "15",
+ // stdHour12 */ 'I': "3",
+ /*stdZeroHour12 */ 'I': "03",
+ // stdMinute */ 'M': "4",
+ /*stdZeroMinute */ 'M': "04",
+ // stdSecond */ 'S': "5",
+ /*stdZeroSecond */ 'S': "05",
+ /*stdLongYear */ 'Y': "2006",
+ /*stdYear */ 'y': "06",
+ /*stdPM */ 'p': "PM",
+ // stdpm */ 'p': "pm",
+ /*stdTZ */ 'Z': "MST",
+ // stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC
+ // stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC
+ /*stdNumTZ */ 'z': "-0700", // always numeric
+ // stdNumShortTZ */ 'b': "-07", // always numeric
+ // stdNumColonTZ */ 'b': "-07:00", // always numeric
+}
+
+// This is an alternative to time.Format because no one knows
+// what date 040305 is supposed to create when used as a 'layout' string
+// this takes standard strftime format options. For a complete list
+// of format options see http://strftime.org/
+func Format(format string, t time.Time) string {
+ retval := make([]byte, 0, len(format))
+ for i, ni := 0, 0; i < len(format); i = ni + 2 {
+ ni = strings.IndexByte(format[i:], '%')
+ if ni < 0 {
+ ni = len(format)
+ } else {
+ ni += i
+ }
+ retval = append(retval, []byte(format[i:ni])...)
+ if ni + 1 < len(format) {
+ c := format[ni + 1]
+ if c == '%' {
+ retval = append(retval, '%')
+ } else {
+ if layoutCmd, ok := conversion[rune(c)]; ok {
+ retval = append(retval, []byte(t.Format(layoutCmd))...)
+ } else {
+ retval = append(retval, '%', c)
+ }
+ }
+ } else {
+ if ni < len(format) {
+ retval = append(retval, '%')
+ }
+ }
+ }
+ return string(retval)
+}
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go
new file mode 100644
index 000000000..45cbca345
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go
@@ -0,0 +1,40 @@
+package strftime
+
+import (
+ "time"
+ "fmt"
+ "testing"
+)
+
+func ExampleFormat() {
+ t := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ t = t.In(utc)
+ fmt.Println(Format("%Y-%m-%d %H:%M:%S", t))
+ // Output:
+ // 2012-06-21 02:12:56
+}
+
+func TestNoLeadingPercentSign(t *testing.T) {
+ tm := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ tm = tm.In(utc)
+ result := Format("aaabbb0123456789%Y", tm)
+ if result != "aaabbb01234567892012" {
+ t.Logf("%s != %s", result, "aaabbb01234567892012")
+ t.Fail()
+ }
+}
+
+
+func TestUnsupported(t *testing.T) {
+ tm := time.Unix(1340244776, 0)
+ utc, _ := time.LoadLocation("UTC")
+ tm = tm.In(utc)
+ result := Format("%0%1%%%2", tm)
+ if result != "%0%1%%2" {
+ t.Logf("%s != %s", result, "%0%1%%2")
+ t.Fail()
+ }
+}
+
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS
new file mode 100644
index 000000000..a6f045160
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS
@@ -0,0 +1,5 @@
+# This source file refers to The gocql Authors for copyright purposes.
+
+Christoph Hack <christoph@tux21b.org>
+Jonathan Rudenberg <jonathan@titanous.com>
+Thorsten von Eicken <tve@rightscale.com>
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE
new file mode 100644
index 000000000..18d25f923
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The gocql Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go
new file mode 100644
index 000000000..a0fd7a5a5
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package uuid can be used to generate and parse universally unique
+// identifiers, a standardized format in the form of a 128 bit number.
+//
+// http://tools.ietf.org/html/rfc4122
+package uuid
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "io"
+ "net"
+ "strconv"
+ "time"
+)
+
+type UUID [16]byte
+
+var hardwareAddr []byte
+
+const (
+ VariantNCSCompat = 0
+ VariantIETF = 2
+ VariantMicrosoft = 6
+ VariantFuture = 7
+)
+
+func init() {
+ if interfaces, err := net.Interfaces(); err == nil {
+ for _, i := range interfaces {
+ if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
+ hardwareAddr = i.HardwareAddr
+ break
+ }
+ }
+ }
+ if hardwareAddr == nil {
+ // If we failed to obtain the MAC address of the current computer,
+ // we will use a randomly generated 6 byte sequence instead and set
+ // the multicast bit as recommended in RFC 4122.
+ hardwareAddr = make([]byte, 6)
+ _, err := io.ReadFull(rand.Reader, hardwareAddr)
+ if err != nil {
+ panic(err)
+ }
+ hardwareAddr[0] = hardwareAddr[0] | 0x01
+ }
+}
+
+// Parse parses a 32 digit hexadecimal number (that might contain hyphens)
+// representing an UUID.
+func Parse(input string) (UUID, error) {
+ var u UUID
+ j := 0
+ for i := 0; i < len(input); i++ {
+ b := input[i]
+ switch {
+ default:
+ fallthrough
+ case j == 32:
+ goto err
+ case b == '-':
+ continue
+ case '0' <= b && b <= '9':
+ b -= '0'
+ case 'a' <= b && b <= 'f':
+ b -= 'a' - 10
+ case 'A' <= b && b <= 'F':
+ b -= 'A' - 10
+ }
+ u[j/2] |= b << byte(^j&1<<2)
+ j++
+ }
+ if j == 32 {
+ return u, nil
+ }
+err:
+ return UUID{}, errors.New("invalid UUID " + strconv.Quote(input))
+}
+
+// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
+// isn't exactly 16 bytes long.
+func FromBytes(input []byte) UUID {
+ var u UUID
+ if len(input) != 16 {
+ panic("UUIDs must be exactly 16 bytes long")
+ }
+ copy(u[:], input)
+ return u
+}
+
+// NewRandom generates a totally random UUID (version 4) as described in
+// RFC 4122.
+func NewRandom() UUID {
+ var u UUID
+ io.ReadFull(rand.Reader, u[:])
+ u[6] &= 0x0F // clear version
+ u[6] |= 0x40 // set version to 4 (random uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+ return u
+}
+
+var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
+
+// NewTime generates a new time based UUID (version 1) as described in RFC
+// 4122. This UUID contains the MAC address of the node that generated the
+// UUID, a timestamp and a sequence number.
+func NewTime() UUID {
+ var u UUID
+
+ now := time.Now().In(time.UTC)
+ t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
+ u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
+ u[4], u[5] = byte(t>>40), byte(t>>32)
+ u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
+
+ var clockSeq [2]byte
+ io.ReadFull(rand.Reader, clockSeq[:])
+ u[8] = clockSeq[1]
+ u[9] = clockSeq[0]
+
+ copy(u[10:], hardwareAddr)
+
+ u[6] |= 0x10 // set version to 1 (time based uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+
+ return u
+}
+
+// String returns the UUID in it's canonical form, a 32 digit hexadecimal
+// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (u UUID) String() string {
+ buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'}
+ hex.Encode(buf[0:], u[0:4])
+ hex.Encode(buf[9:], u[4:6])
+ hex.Encode(buf[14:], u[6:8])
+ hex.Encode(buf[19:], u[8:10])
+ hex.Encode(buf[24:], u[10:])
+ return string(buf[:])
+}
+
+// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
+// (16 bytes) long.
+func (u UUID) Bytes() []byte {
+ return u[:]
+}
+
+// Variant returns the variant of this UUID. This package will only generate
+// UUIDs in the IETF variant.
+func (u UUID) Variant() int {
+ x := u[8]
+ switch byte(0) {
+ case x & 0x80:
+ return VariantNCSCompat
+ case x & 0x40:
+ return VariantIETF
+ case x & 0x20:
+ return VariantMicrosoft
+ }
+ return VariantFuture
+}
+
+// Version extracts the version of this UUID variant. The RFC 4122 describes
+// five kinds of UUIDs.
+func (u UUID) Version() int {
+ return int(u[6] & 0xF0 >> 4)
+}
+
+// Node extracts the MAC address of the node who generated this UUID. It will
+// return nil if the UUID is not a time based UUID (version 1).
+func (u UUID) Node() []byte {
+ if u.Version() != 1 {
+ return nil
+ }
+ return u[10:]
+}
+
+// Timestamp extracts the timestamp information from a time based UUID
+// (version 1).
+func (u UUID) Timestamp() uint64 {
+ if u.Version() != 1 {
+ return 0
+ }
+ return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
+ uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
+ uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
+}
+
+// Time is like Timestamp, except that it returns a time.Time.
+func (u UUID) Time() time.Time {
+ t := u.Timestamp()
+ if t == 0 {
+ return time.Time{}
+ }
+ sec := t / 10000000
+ nsec := t - sec
+ return time.Unix(int64(sec)+timeBase, int64(nsec))
+}
diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go
new file mode 100644
index 000000000..3cef4a31a
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestNil(t *testing.T) {
+ var uuid UUID
+ want, got := "00000000-0000-0000-0000-000000000000", uuid.String()
+ if want != got {
+ t.Fatalf("TestNil: expected %q got %q", want, got)
+ }
+}
+
+var tests = []struct {
+ input string
+ variant int
+ version int
+}{
+ {"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
+ {"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+ {"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+ {"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
+ {"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2},
+ {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3},
+ {"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4},
+ {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5},
+ {"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0},
+ {"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1},
+ {"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0},
+ {"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0},
+}
+
+func TestPredefined(t *testing.T) {
+ for i := range tests {
+ uuid, err := Parse(tests[i].input)
+ if err != nil {
+ t.Errorf("Parse #%d: %v", i, err)
+ continue
+ }
+
+ if str := uuid.String(); str != tests[i].input {
+ t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str)
+ continue
+ }
+
+ if variant := uuid.Variant(); variant != tests[i].variant {
+ t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant)
+ }
+
+ if tests[i].variant == VariantIETF {
+ if version := uuid.Version(); version != tests[i].version {
+ t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version)
+ }
+ }
+ }
+}
+
+func TestNewRandom(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ uuid := NewRandom()
+
+ if variant := uuid.Variant(); variant != VariantIETF {
+ t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+ }
+ if version := uuid.Version(); version != 4 {
+ t.Errorf("wrong version. expected %d got %d", 4, version)
+ }
+ }
+}
+
+func TestNewTime(t *testing.T) {
+ var node []byte
+ timestamp := uint64(0)
+ for i := 0; i < 20; i++ {
+ uuid := NewTime()
+
+ if variant := uuid.Variant(); variant != VariantIETF {
+ t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+ }
+ if version := uuid.Version(); version != 1 {
+ t.Errorf("wrong version. expected %d got %d", 1, version)
+ }
+
+ if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 {
+ t.Errorf("wrong node. expected %x, got %x", node, n)
+ } else if i == 0 {
+ node = n
+ }
+
+ ts := uuid.Timestamp()
+ if ts < timestamp {
+ t.Errorf("timestamps must grow")
+ }
+ timestamp = ts
+ }
+}
diff --git a/vendor/github.com/segmentio/analytics-go/History.md b/vendor/github.com/segmentio/analytics-go/History.md
new file mode 100644
index 000000000..e8538a0cc
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/History.md
@@ -0,0 +1,67 @@
+
+v2.1.1 / 2016-04-26
+===================
+
+ * Fix blocking the goroutine when Close is the first call.
+ * Fix blocking the goroutine when the message queue fills up.
+
+v2.1.0 / 2015-12-28
+===================
+
+ * Add ability to set custom timestamps for messages.
+ * Add ability to set a custom `net/http` client.
+ * Add ability to set a custom logger.
+ * Fix edge case when client would try to upload no messages.
+ * Properly upload in-flight messages when client is asked to shutdown.
+ * Add ability to set `.integrations` field on messages.
+ * Fix resource leak with interval ticker after shutdown.
+ * Add retries and back-off when uploading messages.
+ * Add ability to set custom flush interval.
+
+v2.0.0 / 2015-02-03
+===================
+
+ * rewrite with breaking API changes
+
+v1.2.0 / 2014-09-03
+==================
+
+ * add public .Flush() method
+ * rename .Stop() to .Close()
+
+v1.1.0 / 2014-09-02
+==================
+
+ * add client.Stop() to flash/wait. Closes #7
+
+v1.0.0 / 2014-08-26
+==================
+
+ * fix response close
+ * change comments to be more go-like
+ * change uuid libraries
+
+0.1.2 / 2014-06-11
+==================
+
+ * add runnable example
+ * fix: close body
+
+0.1.1 / 2014-05-31
+==================
+
+ * refactor locking
+
+0.1.0 / 2014-05-22
+==================
+
+ * replace Debug option with debug package
+
+0.0.2 / 2014-05-20
+==================
+
+ * add .Start()
+ * add mutexes
+ * rename BufferSize to FlushAt and FlushInterval to FlushAfter
+ * lower FlushInterval to 5 seconds
+ * lower BufferSize to 20 to match other clients
diff --git a/vendor/github.com/segmentio/analytics-go/Makefile b/vendor/github.com/segmentio/analytics-go/Makefile
new file mode 100644
index 000000000..e896b1b5f
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Makefile
@@ -0,0 +1,10 @@
+vet:
+ @godep go vet ./...
+
+build:
+ @godep go build
+
+test:
+ @godep go test -race -cover ./...
+
+.PHONY: vet build test
diff --git a/vendor/github.com/segmentio/analytics-go/Readme.md b/vendor/github.com/segmentio/analytics-go/Readme.md
new file mode 100644
index 000000000..b521cbe63
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/Readme.md
@@ -0,0 +1,8 @@
+# analytics-go
+
+ Segment analytics client for Go. For additional documentation
+ visit [https://segment.com/docs/libraries/go](https://segment.com/docs/libraries/go/) or view the [godocs](http://godoc.org/github.com/segmentio/analytics-go).
+
+## License
+
+ MIT
diff --git a/vendor/github.com/segmentio/analytics-go/analytics.go b/vendor/github.com/segmentio/analytics-go/analytics.go
new file mode 100644
index 000000000..6ec93fcbf
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/analytics.go
@@ -0,0 +1,407 @@
+package analytics
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sync"
+
+ "bytes"
+ "encoding/json"
+ "errors"
+ "log"
+ "net/http"
+ "time"
+
+ "github.com/jehiah/go-strftime"
+ "github.com/segmentio/backo-go"
+ "github.com/xtgo/uuid"
+)
+
+// Version of the client.
+const Version = "2.1.0"
+
+// Endpoint for the Segment API.
+const Endpoint = "https://api.segment.io"
+
+// DefaultContext of message batches.
+var DefaultContext = map[string]interface{}{
+ "library": map[string]interface{}{
+ "name": "analytics-go",
+ "version": Version,
+ },
+}
+
+// Backoff policy.
+var Backo = backo.DefaultBacko()
+
+// Message interface.
+type message interface {
+ setMessageId(string)
+ setTimestamp(string)
+}
+
+// Message fields common to all.
+type Message struct {
+ Type string `json:"type,omitempty"`
+ MessageId string `json:"messageId,omitempty"`
+ Timestamp string `json:"timestamp,omitempty"`
+ SentAt string `json:"sentAt,omitempty"`
+}
+
+// Batch message.
+type Batch struct {
+ Context map[string]interface{} `json:"context,omitempty"`
+ Messages []interface{} `json:"batch"`
+ Message
+}
+
+// Identify message.
+type Identify struct {
+ Context map[string]interface{} `json:"context,omitempty"`
+ Integrations map[string]interface{} `json:"integrations,omitempty"`
+ Traits map[string]interface{} `json:"traits,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Message
+}
+
+// Group message.
+type Group struct {
+ Context map[string]interface{} `json:"context,omitempty"`
+ Integrations map[string]interface{} `json:"integrations,omitempty"`
+ Traits map[string]interface{} `json:"traits,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ GroupId string `json:"groupId"`
+ Message
+}
+
+// Track message.
+type Track struct {
+ Context map[string]interface{} `json:"context,omitempty"`
+ Integrations map[string]interface{} `json:"integrations,omitempty"`
+ Properties map[string]interface{} `json:"properties,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Event string `json:"event"`
+ Message
+}
+
+// Page message.
+type Page struct {
+ Context map[string]interface{} `json:"context,omitempty"`
+ Integrations map[string]interface{} `json:"integrations,omitempty"`
+ Traits map[string]interface{} `json:"properties,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Category string `json:"category,omitempty"`
+ Name string `json:"name,omitempty"`
+ Message
+}
+
+// Alias message.
+type Alias struct {
+ PreviousId string `json:"previousId"`
+ UserId string `json:"userId"`
+ Message
+}
+
+// Client which batches messages and flushes at the given Interval or
+// when the Size limit is exceeded. Set Verbose to true to enable
+// logging output.
+type Client struct {
+ Endpoint string
+ // Interval represents the duration at which messages are flushed. It may be
+ // configured only before any messages are enqueued.
+ Interval time.Duration
+ Size int
+ Logger *log.Logger
+ Verbose bool
+ Client http.Client
+ key string
+ msgs chan interface{}
+ quit chan struct{}
+ shutdown chan struct{}
+ uid func() string
+ now func() time.Time
+ once sync.Once
+ wg sync.WaitGroup
+
+ // These synchronization primitives are used to control how many goroutines
+ // are spawned by the client for uploads.
+ upmtx sync.Mutex
+ upcond sync.Cond
+ upcount int
+}
+
+// New client with write key.
+func New(key string) *Client {
+ c := &Client{
+ Endpoint: Endpoint,
+ Interval: 5 * time.Second,
+ Size: 250,
+ Logger: log.New(os.Stderr, "segment ", log.LstdFlags),
+ Verbose: false,
+ Client: *http.DefaultClient,
+ key: key,
+ msgs: make(chan interface{}, 100),
+ quit: make(chan struct{}),
+ shutdown: make(chan struct{}),
+ now: time.Now,
+ uid: uid,
+ }
+
+ c.upcond.L = &c.upmtx
+ return c
+}
+
+// Alias buffers an "alias" message.
+func (c *Client) Alias(msg *Alias) error {
+ if msg.UserId == "" {
+ return errors.New("You must pass a 'userId'.")
+ }
+
+ if msg.PreviousId == "" {
+ return errors.New("You must pass a 'previousId'.")
+ }
+
+ msg.Type = "alias"
+ c.queue(msg)
+
+ return nil
+}
+
+// Page buffers an "page" message.
+func (c *Client) Page(msg *Page) error {
+ if msg.UserId == "" && msg.AnonymousId == "" {
+ return errors.New("You must pass either an 'anonymousId' or 'userId'.")
+ }
+
+ msg.Type = "page"
+ c.queue(msg)
+
+ return nil
+}
+
+// Group buffers an "group" message.
+func (c *Client) Group(msg *Group) error {
+ if msg.GroupId == "" {
+ return errors.New("You must pass a 'groupId'.")
+ }
+
+ if msg.UserId == "" && msg.AnonymousId == "" {
+ return errors.New("You must pass either an 'anonymousId' or 'userId'.")
+ }
+
+ msg.Type = "group"
+ c.queue(msg)
+
+ return nil
+}
+
+// Identify buffers an "identify" message.
+func (c *Client) Identify(msg *Identify) error {
+ if msg.UserId == "" && msg.AnonymousId == "" {
+ return errors.New("You must pass either an 'anonymousId' or 'userId'.")
+ }
+
+ msg.Type = "identify"
+ c.queue(msg)
+
+ return nil
+}
+
+// Track buffers an "track" message.
+func (c *Client) Track(msg *Track) error {
+ if msg.Event == "" {
+ return errors.New("You must pass 'event'.")
+ }
+
+ if msg.UserId == "" && msg.AnonymousId == "" {
+ return errors.New("You must pass either an 'anonymousId' or 'userId'.")
+ }
+
+ msg.Type = "track"
+ c.queue(msg)
+
+ return nil
+}
+
+func (c *Client) startLoop() {
+ go c.loop()
+}
+
+// Queue message.
+func (c *Client) queue(msg message) {
+ c.once.Do(c.startLoop)
+ msg.setMessageId(c.uid())
+ msg.setTimestamp(timestamp(c.now()))
+ c.msgs <- msg
+}
+
+// Close and flush metrics.
+func (c *Client) Close() error {
+ c.once.Do(c.startLoop)
+ c.quit <- struct{}{}
+ close(c.msgs)
+ <-c.shutdown
+ return nil
+}
+
+func (c *Client) sendAsync(msgs []interface{}) {
+ c.upmtx.Lock()
+ for c.upcount == 1000 {
+ c.upcond.Wait()
+ }
+ c.upcount++
+ c.upmtx.Unlock()
+ c.wg.Add(1)
+ go func() {
+ err := c.send(msgs)
+ if err != nil {
+ c.logf(err.Error())
+ }
+ c.upmtx.Lock()
+ c.upcount--
+ c.upcond.Signal()
+ c.upmtx.Unlock()
+ c.wg.Done()
+ }()
+}
+
+// Send batch request.
+func (c *Client) send(msgs []interface{}) error {
+ if len(msgs) == 0 {
+ return nil
+ }
+
+ batch := new(Batch)
+ batch.Messages = msgs
+ batch.MessageId = c.uid()
+ batch.SentAt = timestamp(c.now())
+ batch.Context = DefaultContext
+
+ b, err := json.Marshal(batch)
+ if err != nil {
+ return fmt.Errorf("error marshalling msgs: %s", err)
+ }
+
+ for i := 0; i < 10; i++ {
+ if err = c.upload(b); err == nil {
+ return nil
+ }
+ Backo.Sleep(i)
+ }
+
+ return err
+}
+
+// Upload serialized batch message.
+func (c *Client) upload(b []byte) error {
+ url := c.Endpoint + "/v1/batch"
+ req, err := http.NewRequest("POST", url, bytes.NewReader(b))
+ if err != nil {
+ return fmt.Errorf("error creating request: %s", err)
+ }
+
+ req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Content-Length", string(len(b)))
+ req.SetBasicAuth(c.key, "")
+
+ res, err := c.Client.Do(req)
+ if err != nil {
+ return fmt.Errorf("error sending request: %s", err)
+ }
+ defer res.Body.Close()
+
+ if res.StatusCode < 400 {
+ c.verbose("response %s", res.Status)
+ return nil
+ }
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return fmt.Errorf("error reading response body: %s", err)
+ }
+
+ return fmt.Errorf("response %s: %d – %s", res.Status, res.StatusCode, string(body))
+}
+
+// Batch loop.
+func (c *Client) loop() {
+ var msgs []interface{}
+ tick := time.NewTicker(c.Interval)
+
+ for {
+ select {
+ case msg := <-c.msgs:
+ c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
+ msgs = append(msgs, msg)
+ if len(msgs) == c.Size {
+ c.verbose("exceeded %d messages – flushing", c.Size)
+ c.sendAsync(msgs)
+ msgs = make([]interface{}, 0, c.Size)
+ }
+ case <-tick.C:
+ if len(msgs) > 0 {
+ c.verbose("interval reached - flushing %d", len(msgs))
+ c.sendAsync(msgs)
+ msgs = make([]interface{}, 0, c.Size)
+ } else {
+ c.verbose("interval reached – nothing to send")
+ }
+ case <-c.quit:
+ tick.Stop()
+ c.verbose("exit requested – draining msgs")
+ // drain the msg channel.
+ for msg := range c.msgs {
+ c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg)
+ msgs = append(msgs, msg)
+ }
+ c.verbose("exit requested – flushing %d", len(msgs))
+ c.sendAsync(msgs)
+ c.wg.Wait()
+ c.verbose("exit")
+ c.shutdown <- struct{}{}
+ return
+ }
+ }
+}
+
+// Verbose log.
+func (c *Client) verbose(msg string, args ...interface{}) {
+ if c.Verbose {
+ c.Logger.Printf(msg, args...)
+ }
+}
+
+// Unconditional log.
+func (c *Client) logf(msg string, args ...interface{}) {
+ c.Logger.Printf(msg, args...)
+}
+
+// Set message timestamp if one is not already set.
+func (m *Message) setTimestamp(s string) {
+ if m.Timestamp == "" {
+ m.Timestamp = s
+ }
+}
+
+// Set message id.
+func (m *Message) setMessageId(s string) {
+ if m.MessageId == "" {
+ m.MessageId = s
+ }
+}
+
+// Return formatted timestamp.
+func timestamp(t time.Time) string {
+ return strftime.Format("%Y-%m-%dT%H:%M:%S%z", t)
+}
+
+// Return uuid string.
+func uid() string {
+ return uuid.NewRandom().String()
+}
diff --git a/vendor/github.com/segmentio/analytics-go/analytics_test.go b/vendor/github.com/segmentio/analytics-go/analytics_test.go
new file mode 100644
index 000000000..c6f0dc4f2
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/analytics_test.go
@@ -0,0 +1,478 @@
+package analytics
+
+import "net/http/httptest"
+import "encoding/json"
+import "net/http"
+import "testing"
+import "bytes"
+import "time"
+import "fmt"
+import "io"
+
+func mockId() string { return "I'm unique" }
+
+func mockTime() time.Time {
+ // time.Unix(0, 0) fails on Circle
+ return time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+}
+
+func mockServer() (chan []byte, *httptest.Server) {
+ done := make(chan []byte, 1)
+
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ buf := bytes.NewBuffer(nil)
+ io.Copy(buf, r.Body)
+
+ var v interface{}
+ err := json.Unmarshal(buf.Bytes(), &v)
+ if err != nil {
+ panic(err)
+ }
+
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ panic(err)
+ }
+
+ done <- b
+ }))
+
+ return done, server
+}
+
+func ExampleTrack() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 1
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ })
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleClose() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ })
+
+ client.Close()
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleInterval() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ })
+
+ // Will flush in 5 seconds (default interval).
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleTrackWithTimestampSet() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 1
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ Message: Message{
+ Timestamp: timestamp(time.Date(2015, time.July, 10, 23, 0, 0, 0, time.UTC)),
+ },
+ })
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2015-07-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleTrackWithMessageIdSet() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 1
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ Message: Message{
+ MessageId: "abc",
+ },
+ })
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "abc",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleTrack_context() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 1
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ Context: map[string]interface{}{
+ "whatever": "here",
+ },
+ })
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "context": {
+ // "whatever": "here"
+ // },
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleTrack_many() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 3
+
+ for i := 0; i < 5; i++ {
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": i,
+ },
+ })
+ }
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "version": 0
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // },
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "version": 1
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // },
+ // {
+ // "event": "Download",
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "version": 2
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+func ExampleTrackWithIntegrations() {
+ body, server := mockServer()
+ defer server.Close()
+
+ client := New("h97jamjwbh")
+ client.Endpoint = server.URL
+ client.now = mockTime
+ client.uid = mockId
+ client.Size = 1
+
+ client.Track(&Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ Integrations: map[string]interface{}{
+ "All": true,
+ "Intercom": false,
+ "Mixpanel": true,
+ },
+ })
+
+ fmt.Printf("%s\n", <-body)
+ // Output:
+ // {
+ // "batch": [
+ // {
+ // "event": "Download",
+ // "integrations": {
+ // "All": true,
+ // "Intercom": false,
+ // "Mixpanel": true
+ // },
+ // "messageId": "I'm unique",
+ // "properties": {
+ // "application": "Segment Desktop",
+ // "platform": "osx",
+ // "version": "1.1.0"
+ // },
+ // "timestamp": "2009-11-10T23:00:00+0000",
+ // "type": "track",
+ // "userId": "123456"
+ // }
+ // ],
+ // "context": {
+ // "library": {
+ // "name": "analytics-go",
+ // "version": "2.1.0"
+ // }
+ // },
+ // "messageId": "I'm unique",
+ // "sentAt": "2009-11-10T23:00:00+0000"
+ // }
+}
+
+// Tests that calling Close right after creating the client object doesn't
+// block.
+// Bug: https://github.com/segmentio/analytics-go/issues/43
+func TestCloseFinish(_ *testing.T) {
+ c := New("test")
+ c.Close()
+}
diff --git a/vendor/github.com/segmentio/analytics-go/circle.yml b/vendor/github.com/segmentio/analytics-go/circle.yml
new file mode 100644
index 000000000..11733e196
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/circle.yml
@@ -0,0 +1,18 @@
+machine:
+ environment:
+ IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
+
+dependencies:
+ pre:
+ - go get github.com/tools/godep
+
+ override:
+ - mkdir -p "$GOPATH/src/$IMPORT_PATH"
+ - rsync -azC --delete ./ "$GOPATH/src/$IMPORT_PATH/"
+
+test:
+ pre:
+ - make vet
+
+ override:
+ - make test
diff --git a/vendor/github.com/segmentio/analytics-go/cli/cli.go b/vendor/github.com/segmentio/analytics-go/cli/cli.go
new file mode 100644
index 000000000..9d1f940c9
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/cli/cli.go
@@ -0,0 +1,174 @@
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+ "reflect"
+
+ "github.com/segmentio/analytics-go"
+ "github.com/tj/docopt"
+)
+
+const Usage = `
+Analytics Go CLI
+
+Usage:
+ analytics track <event> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics screen <name> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics page <name> [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics identify [--traits=<traits>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics group --groupId=<groupId> [--traits=<traits>] [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--userId=<userId>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics alias --userId=<userId> --previousId=<previousId> [--traits=<traits>] [--properties=<properties>] [--context=<context>] [--writeKey=<writeKey>] [--anonymousId=<anonymousId>] [--integrations=<integrations>] [--timestamp=<timestamp>]
+ analytics -h | --help
+ analytics --version
+
+Options:
+ -h --help Show this screen.
+ --version Show version.
+`
+
+func main() {
+ arguments, err := docopt.Parse(Usage, nil, true, "Anaytics Go CLI", false)
+ check(err)
+
+ writeKey := getOptionalString(arguments, "--writeKey")
+ if writeKey == "" {
+ writeKey = os.Getenv("SEGMENT_WRITE_KEY")
+ if writeKey == "" {
+ log.Fatal("either $SEGMENT_WRITE_KEY or --writeKey must be provided")
+ }
+ }
+
+ client := analytics.New(writeKey)
+ client.Size = 1
+ client.Verbose = true
+
+ if arguments["track"].(bool) {
+ m := &analytics.Track{
+ Event: arguments["<event>"].(string),
+ }
+ properties := getOptionalString(arguments, "--properties")
+ if properties != "" {
+ var parsedProperties map[string]interface{}
+ err := json.Unmarshal([]byte(properties), &parsedProperties)
+ check(err)
+ m.Properties = parsedProperties
+ }
+
+ setCommonFields(m, arguments)
+
+ check(client.Track(m))
+ }
+
+ if arguments["screen"].(bool) || arguments["page"].(bool) {
+ m := &analytics.Page{
+ Name: arguments["<name>"].(string),
+ }
+ /* Bug in Go library - page has traits not properties.
+ properties := getOptionalString(arguments, "--properties")
+ if properties != "" {
+ var parsedProperties map[string]interface{}
+ err := json.Unmarshal([]byte(properties), &parsedProperties)
+ check(err)
+ t.Properties = parsedProperties
+ }
+ */
+
+ setCommonFields(m, arguments)
+
+ check(client.Page(m))
+ }
+
+ if arguments["identify"].(bool) {
+ m := &analytics.Identify{}
+ traits := getOptionalString(arguments, "--traits")
+ if traits != "" {
+ var parsedTraits map[string]interface{}
+ err := json.Unmarshal([]byte(traits), &parsedTraits)
+ check(err)
+ m.Traits = parsedTraits
+ }
+
+ setCommonFields(m, arguments)
+
+ check(client.Identify(m))
+ }
+
+ if arguments["group"].(bool) {
+ m := &analytics.Group{
+ GroupId: arguments["--groupId"].(string),
+ }
+ traits := getOptionalString(arguments, "--traits")
+ if traits != "" {
+ var parsedTraits map[string]interface{}
+ err := json.Unmarshal([]byte(traits), &parsedTraits)
+ check(err)
+ m.Traits = parsedTraits
+ }
+
+ setCommonFields(m, arguments)
+
+ check(client.Group(m))
+ }
+
+ if arguments["alias"].(bool) {
+ m := &analytics.Alias{
+ PreviousId: arguments["--previousId"].(string),
+ }
+
+ setCommonFields(m, arguments)
+
+ check(client.Alias(m))
+ }
+
+ client.Close()
+}
+
+func setCommonFields(message interface{}, arguments map[string]interface{}) {
+ userId := getOptionalString(arguments, "--userId")
+ if userId != "" {
+ setFieldValue(message, "UserId", userId)
+ }
+ anonymousId := getOptionalString(arguments, "--anonymousId")
+ if anonymousId != "" {
+ setFieldValue(message, "AnonymousId", anonymousId)
+ }
+ integrations := getOptionalString(arguments, "--integrations")
+ if integrations != "" {
+ var parsedIntegrations map[string]interface{}
+ err := json.Unmarshal([]byte(integrations), &parsedIntegrations)
+ check(err)
+ setFieldValue(message, "Integrations", parsedIntegrations)
+ }
+ context := getOptionalString(arguments, "--context")
+ if context != "" {
+ var parsedContext map[string]interface{}
+ err := json.Unmarshal([]byte(context), &parsedContext)
+ check(err)
+ setFieldValue(message, "Context", parsedContext)
+
+ }
+ timestamp := getOptionalString(arguments, "--timestamp")
+ if timestamp != "" {
+ setFieldValue(message, "Timestamp", timestamp)
+ }
+}
+
+func setFieldValue(target interface{}, field string, value interface{}) {
+ reflect.ValueOf(target).Elem().FieldByName(field).Set(reflect.ValueOf(value))
+}
+
+func getOptionalString(m map[string]interface{}, k string) string {
+ v := m[k]
+ if v == nil {
+ return ""
+ }
+ return v.(string)
+}
+
+func check(err error) {
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/vendor/github.com/segmentio/analytics-go/examples/track.go b/vendor/github.com/segmentio/analytics-go/examples/track.go
new file mode 100644
index 000000000..484c38efa
--- /dev/null
+++ b/vendor/github.com/segmentio/analytics-go/examples/track.go
@@ -0,0 +1,36 @@
+package main
+
+import "github.com/segmentio/analytics-go"
+import "time"
+
+func main() {
+ client := analytics.New("h97jamjwbh")
+ client.Interval = 30 * time.Second
+ client.Size = 100
+ client.Verbose = true
+
+ done := time.After(3 * time.Second)
+ tick := time.Tick(50 * time.Millisecond)
+
+out:
+ for {
+ select {
+ case <-done:
+ println("exiting")
+ break out
+ case <-tick:
+ client.Track(&analytics.Track{
+ Event: "Download",
+ UserId: "123456",
+ Properties: map[string]interface{}{
+ "application": "Segment Desktop",
+ "version": "1.1.0",
+ "platform": "osx",
+ },
+ })
+ }
+ }
+
+ println("flushing")
+ client.Close()
+}
diff --git a/vendor/github.com/segmentio/backo-go/README.md b/vendor/github.com/segmentio/backo-go/README.md
new file mode 100644
index 000000000..1362becf1
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/README.md
@@ -0,0 +1,80 @@
+Backo [![GoDoc](http://godoc.org/github.com/segmentio/backo-go?status.png)](http://godoc.org/github.com/segmentio/backo-go)
+-----
+
+Exponential backoff for Go (Go port of segmentio/backo).
+
+
+Usage
+-----
+
+```go
+import "github.com/segmentio/backo-go"
+
+// Create a Backo instance.
+backo := backo.NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
+// OR with defaults.
+backo := backo.DefaultBacko()
+
+// Use the ticker API.
+ticker := b.NewTicker()
+for {
+ timeout := time.After(5 * time.Minute)
+ select {
+ case <-ticker.C:
+ fmt.Println("ticked")
+ case <- timeout:
+ fmt.Println("timed out")
+ }
+}
+
+// Or simply work with backoff intervals directly.
+for i := 0; i < n; i++ {
+ // Sleep the current goroutine.
+ backo.Sleep(i)
+ // Retrieve the duration manually.
+ duration := backo.Duration(i)
+}
+```
+
+License
+-------
+
+```
+WWWWWW||WWWWWW
+ W W W||W W W
+ ||
+ ( OO )__________
+ / | \
+ /o o| MIT \
+ \___/||_||__||_|| *
+ || || || ||
+ _||_|| _||_||
+ (__|__|(__|__|
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Segment, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+
+
+ [1]: http://github.com/segmentio/backo-java
+ [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.segment.backo&a=backo&v=LATEST \ No newline at end of file
diff --git a/vendor/github.com/segmentio/backo-go/backo.go b/vendor/github.com/segmentio/backo-go/backo.go
new file mode 100644
index 000000000..6f7b6d5e2
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/backo.go
@@ -0,0 +1,83 @@
+package backo
+
+import (
+ "math"
+ "math/rand"
+ "time"
+)
+
+type Backo struct {
+ base time.Duration
+ factor uint8
+ jitter float64
+ cap time.Duration
+}
+
+// Creates a backo instance with the given parameters
+func NewBacko(base time.Duration, factor uint8, jitter float64, cap time.Duration) *Backo {
+ return &Backo{base, factor, jitter, cap}
+}
+
+// Creates a backo instance with the following defaults:
+// base: 100 milliseconds
+// factor: 2
+// jitter: 0
+// cap: 10 seconds
+func DefaultBacko() *Backo {
+ return NewBacko(time.Millisecond*100, 2, 0, time.Second*10)
+}
+
+// Duration returns the backoff interval for the given attempt.
+func (backo *Backo) Duration(attempt int) time.Duration {
+ duration := float64(backo.base) * math.Pow(float64(backo.factor), float64(attempt))
+
+ if backo.jitter != 0 {
+ random := rand.Float64()
+ deviation := math.Floor(random * backo.jitter * duration)
+ if (int(math.Floor(random*10)) & 1) == 0 {
+ duration = duration - deviation
+ } else {
+ duration = duration + deviation
+ }
+ }
+
+ duration = math.Min(float64(duration), float64(backo.cap))
+ return time.Duration(duration)
+}
+
+// Sleep pauses the current goroutine for the backoff interval for the given attempt.
+func (backo *Backo) Sleep(attempt int) {
+ duration := backo.Duration(attempt)
+ time.Sleep(duration)
+}
+
+type Ticker struct {
+ done chan struct{}
+ C <-chan time.Time
+}
+
+func (b *Backo) NewTicker() *Ticker {
+ c := make(chan time.Time, 1)
+ ticker := &Ticker{
+ done: make(chan struct{}, 1),
+ C: c,
+ }
+
+ go func() {
+ for i := 0; ; i++ {
+ select {
+ case t := <-time.After(b.Duration(i)):
+ c <- t
+ case <-ticker.done:
+ close(c)
+ return
+ }
+ }
+ }()
+
+ return ticker
+}
+
+func (t *Ticker) Stop() {
+ t.done <- struct{}{}
+}
diff --git a/vendor/github.com/segmentio/backo-go/backo_test.go b/vendor/github.com/segmentio/backo-go/backo_test.go
new file mode 100644
index 000000000..89933acf7
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/backo_test.go
@@ -0,0 +1,77 @@
+package backo
+
+import (
+ "fmt"
+ "math"
+ "testing"
+ "time"
+
+ "github.com/bmizerany/assert"
+)
+
+// Tests default backo behaviour.
+func TestDefaults(t *testing.T) {
+ backo := DefaultBacko()
+
+ assert.Equal(t, milliseconds(100), backo.Duration(0))
+ assert.Equal(t, milliseconds(200), backo.Duration(1))
+ assert.Equal(t, milliseconds(400), backo.Duration(2))
+ assert.Equal(t, milliseconds(800), backo.Duration(3))
+}
+
+// Tests backo does not exceed cap.
+func TestCap(t *testing.T) {
+ backo := NewBacko(milliseconds(100), 2, 0, milliseconds(600))
+
+ assert.Equal(t, milliseconds(100), backo.Duration(0))
+ assert.Equal(t, milliseconds(200), backo.Duration(1))
+ assert.Equal(t, milliseconds(400), backo.Duration(2))
+ assert.Equal(t, milliseconds(600), backo.Duration(3))
+}
+
+// Tests that jitter adds randomness.
+func TestJitter(t *testing.T) {
+ defaultBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
+ jitterBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
+
+ // TODO: Check jittered durations are within a range.
+ assert.NotEqual(t, jitterBacko.Duration(0), defaultBacko.Duration(0))
+ assert.NotEqual(t, jitterBacko.Duration(1), defaultBacko.Duration(1))
+ assert.NotEqual(t, jitterBacko.Duration(2), defaultBacko.Duration(2))
+ assert.NotEqual(t, jitterBacko.Duration(3), defaultBacko.Duration(3))
+}
+
+func ExampleBacko_BackoffDefault() {
+ b := DefaultBacko()
+ ticker := b.NewTicker()
+
+ for i := 0; i < 6; i++ {
+ start := time.Now()
+ select {
+ case t := <-ticker.C:
+ fmt.Println(nearest10Millis(t.Sub(start)))
+ }
+ }
+
+ ticker.Stop()
+
+ // Output:
+ // 100
+ // 200
+ // 400
+ // 800
+ // 1600
+ // 3200
+}
+
+func nearest10Millis(d time.Duration) float64 {
+ // Typically d is something like 11 or 21, so do some magic to round the
+ // durations to the nearest 10. We divide d by 10, floor it, and multiply it
+ // by 10 again.
+ return math.Floor(float64(d/time.Millisecond/10) * 10)
+}
+
+// Returns the given milliseconds as time.Duration
+func milliseconds(ms int64) time.Duration {
+ return time.Duration(ms * 1000 * 1000)
+}
diff --git a/vendor/github.com/xtgo/uuid/AUTHORS b/vendor/github.com/xtgo/uuid/AUTHORS
new file mode 100644
index 000000000..a6f045160
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/AUTHORS
@@ -0,0 +1,5 @@
+# This source file refers to The gocql Authors for copyright purposes.
+
+Christoph Hack <christoph@tux21b.org>
+Jonathan Rudenberg <jonathan@titanous.com>
+Thorsten von Eicken <tve@rightscale.com>
diff --git a/vendor/github.com/xtgo/uuid/LICENSE b/vendor/github.com/xtgo/uuid/LICENSE
new file mode 100644
index 000000000..18d25f923
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The gocql Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/xtgo/uuid/uuid.go b/vendor/github.com/xtgo/uuid/uuid.go
new file mode 100644
index 000000000..a0fd7a5a5
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/uuid.go
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package uuid can be used to generate and parse universally unique
+// identifiers, a standardized format in the form of a 128 bit number.
+//
+// http://tools.ietf.org/html/rfc4122
+package uuid
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "io"
+ "net"
+ "strconv"
+ "time"
+)
+
+type UUID [16]byte
+
+var hardwareAddr []byte
+
+const (
+ VariantNCSCompat = 0
+ VariantIETF = 2
+ VariantMicrosoft = 6
+ VariantFuture = 7
+)
+
+func init() {
+ if interfaces, err := net.Interfaces(); err == nil {
+ for _, i := range interfaces {
+ if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
+ hardwareAddr = i.HardwareAddr
+ break
+ }
+ }
+ }
+ if hardwareAddr == nil {
+ // If we failed to obtain the MAC address of the current computer,
+ // we will use a randomly generated 6 byte sequence instead and set
+ // the multicast bit as recommended in RFC 4122.
+ hardwareAddr = make([]byte, 6)
+ _, err := io.ReadFull(rand.Reader, hardwareAddr)
+ if err != nil {
+ panic(err)
+ }
+ hardwareAddr[0] = hardwareAddr[0] | 0x01
+ }
+}
+
+// Parse parses a 32 digit hexadecimal number (that might contain hyphens)
+// representing an UUID.
+func Parse(input string) (UUID, error) {
+ var u UUID
+ j := 0
+ for i := 0; i < len(input); i++ {
+ b := input[i]
+ switch {
+ default:
+ fallthrough
+ case j == 32:
+ goto err
+ case b == '-':
+ continue
+ case '0' <= b && b <= '9':
+ b -= '0'
+ case 'a' <= b && b <= 'f':
+ b -= 'a' - 10
+ case 'A' <= b && b <= 'F':
+ b -= 'A' - 10
+ }
+ u[j/2] |= b << byte(^j&1<<2)
+ j++
+ }
+ if j == 32 {
+ return u, nil
+ }
+err:
+ return UUID{}, errors.New("invalid UUID " + strconv.Quote(input))
+}
+
+// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
+// isn't exactly 16 bytes long.
+func FromBytes(input []byte) UUID {
+ var u UUID
+ if len(input) != 16 {
+ panic("UUIDs must be exactly 16 bytes long")
+ }
+ copy(u[:], input)
+ return u
+}
+
+// NewRandom generates a totally random UUID (version 4) as described in
+// RFC 4122.
+func NewRandom() UUID {
+ var u UUID
+ io.ReadFull(rand.Reader, u[:])
+ u[6] &= 0x0F // clear version
+ u[6] |= 0x40 // set version to 4 (random uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+ return u
+}
+
+var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
+
+// NewTime generates a new time based UUID (version 1) as described in RFC
+// 4122. This UUID contains the MAC address of the node that generated the
+// UUID, a timestamp and a sequence number.
+func NewTime() UUID {
+ var u UUID
+
+ now := time.Now().In(time.UTC)
+ t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
+ u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
+ u[4], u[5] = byte(t>>40), byte(t>>32)
+ u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
+
+ var clockSeq [2]byte
+ io.ReadFull(rand.Reader, clockSeq[:])
+ u[8] = clockSeq[1]
+ u[9] = clockSeq[0]
+
+ copy(u[10:], hardwareAddr)
+
+ u[6] |= 0x10 // set version to 1 (time based uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+
+ return u
+}
+
+// String returns the UUID in it's canonical form, a 32 digit hexadecimal
+// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (u UUID) String() string {
+ buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'}
+ hex.Encode(buf[0:], u[0:4])
+ hex.Encode(buf[9:], u[4:6])
+ hex.Encode(buf[14:], u[6:8])
+ hex.Encode(buf[19:], u[8:10])
+ hex.Encode(buf[24:], u[10:])
+ return string(buf[:])
+}
+
+// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
+// (16 bytes) long.
+func (u UUID) Bytes() []byte {
+ return u[:]
+}
+
+// Variant returns the variant of this UUID. This package will only generate
+// UUIDs in the IETF variant.
+func (u UUID) Variant() int {
+ x := u[8]
+ switch byte(0) {
+ case x & 0x80:
+ return VariantNCSCompat
+ case x & 0x40:
+ return VariantIETF
+ case x & 0x20:
+ return VariantMicrosoft
+ }
+ return VariantFuture
+}
+
+// Version extracts the version of this UUID variant. The RFC 4122 describes
+// five kinds of UUIDs.
+func (u UUID) Version() int {
+ return int(u[6] & 0xF0 >> 4)
+}
+
+// Node extracts the MAC address of the node who generated this UUID. It will
+// return nil if the UUID is not a time based UUID (version 1).
+func (u UUID) Node() []byte {
+ if u.Version() != 1 {
+ return nil
+ }
+ return u[10:]
+}
+
+// Timestamp extracts the timestamp information from a time based UUID
+// (version 1).
+func (u UUID) Timestamp() uint64 {
+ if u.Version() != 1 {
+ return 0
+ }
+ return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
+ uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
+ uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
+}
+
+// Time is like Timestamp, except that it returns a time.Time.
+func (u UUID) Time() time.Time {
+ t := u.Timestamp()
+ if t == 0 {
+ return time.Time{}
+ }
+ sec := t / 10000000
+ nsec := t - sec
+ return time.Unix(int64(sec)+timeBase, int64(nsec))
+}
diff --git a/vendor/github.com/xtgo/uuid/uuid_test.go b/vendor/github.com/xtgo/uuid/uuid_test.go
new file mode 100644
index 000000000..3cef4a31a
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/uuid_test.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestNil(t *testing.T) {
+ var uuid UUID
+ want, got := "00000000-0000-0000-0000-000000000000", uuid.String()
+ if want != got {
+ t.Fatalf("TestNil: expected %q got %q", want, got)
+ }
+}
+
+var tests = []struct {
+ input string
+ variant int
+ version int
+}{
+ {"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
+ {"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+ {"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+ {"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
+ {"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2},
+ {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3},
+ {"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4},
+ {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5},
+ {"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0},
+ {"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1},
+ {"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0},
+ {"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0},
+}
+
+func TestPredefined(t *testing.T) {
+ for i := range tests {
+ uuid, err := Parse(tests[i].input)
+ if err != nil {
+ t.Errorf("Parse #%d: %v", i, err)
+ continue
+ }
+
+ if str := uuid.String(); str != tests[i].input {
+ t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str)
+ continue
+ }
+
+ if variant := uuid.Variant(); variant != tests[i].variant {
+ t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant)
+ }
+
+ if tests[i].variant == VariantIETF {
+ if version := uuid.Version(); version != tests[i].version {
+ t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version)
+ }
+ }
+ }
+}
+
+func TestNewRandom(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ uuid := NewRandom()
+
+ if variant := uuid.Variant(); variant != VariantIETF {
+ t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+ }
+ if version := uuid.Version(); version != 4 {
+ t.Errorf("wrong version. expected %d got %d", 4, version)
+ }
+ }
+}
+
+func TestNewTime(t *testing.T) {
+ var node []byte
+ timestamp := uint64(0)
+ for i := 0; i < 20; i++ {
+ uuid := NewTime()
+
+ if variant := uuid.Variant(); variant != VariantIETF {
+ t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+ }
+ if version := uuid.Version(); version != 1 {
+ t.Errorf("wrong version. expected %d got %d", 1, version)
+ }
+
+ if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 {
+ t.Errorf("wrong node. expected %x, got %x", node, n)
+ } else if i == 0 {
+ node = n
+ }
+
+ ts := uuid.Timestamp()
+ if ts < timestamp {
+ t.Errorf("timestamps must grow")
+ }
+ timestamp = ts
+ }
+}
diff --git a/webapp/components/admin_console/external_service_settings.jsx b/webapp/components/admin_console/external_service_settings.jsx
index 59a129fc0..53fdbfb53 100644
--- a/webapp/components/admin_console/external_service_settings.jsx
+++ b/webapp/components/admin_console/external_service_settings.jsx
@@ -20,15 +20,12 @@ export default class ExternalServiceSettings extends AdminSettings {
}
getConfigFromState(config) {
- config.ServiceSettings.SegmentDeveloperKey = this.state.segmentDeveloperKey;
config.ServiceSettings.GoogleDeveloperKey = this.state.googleDeveloperKey;
-
return config;
}
getStateFromConfig(config) {
return {
- segmentDeveloperKey: config.ServiceSettings.SegmentDeveloperKey,
googleDeveloperKey: config.ServiceSettings.GoogleDeveloperKey
};
}
@@ -48,24 +45,6 @@ export default class ExternalServiceSettings extends AdminSettings {
return (
<SettingsGroup>
<TextSetting
- id='segmentDeveloperKey'
- label={
- <FormattedMessage
- id='admin.service.segmentTitle'
- defaultMessage='Segment Write Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.segmentExample', 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"')}
- helpText={
- <FormattedMessage
- id='admin.service.segmentDescription'
- defaultMessage='Segment.com is an online service that can be optionally used to track detailed system statistics. You can obtain a key by signing-up for a free account at Segment.com.'
- />
- }
- value={this.state.segmentDeveloperKey}
- onChange={this.handleChange}
- />
- <TextSetting
id='googleDeveloperKey'
label={
<FormattedMessage
diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx
index 31abca316..135369942 100644
--- a/webapp/components/admin_console/log_settings.jsx
+++ b/webapp/components/admin_console/log_settings.jsx
@@ -8,7 +8,7 @@ import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage} from 'react-intl';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
@@ -29,6 +29,7 @@ export default class LogSettings extends AdminSettings {
config.LogSettings.FileLocation = this.state.fileLocation;
config.LogSettings.FileFormat = this.state.fileFormat;
config.LogSettings.EnableWebhookDebugging = this.state.enableWebhookDebugging;
+ config.LogSettings.EnableDiagnostics = this.state.enableDiagnostics;
return config;
}
@@ -41,7 +42,8 @@ export default class LogSettings extends AdminSettings {
fileLevel: config.LogSettings.FileLevel,
fileLocation: config.LogSettings.FileLocation,
fileFormat: config.LogSettings.FileFormat,
- enableWebhookDebugging: config.LogSettings.EnableWebhookDebugging
+ enableWebhookDebugging: config.LogSettings.EnableWebhookDebugging,
+ enableDiagnostics: config.LogSettings.EnableDiagnostics
};
}
@@ -187,6 +189,23 @@ export default class LogSettings extends AdminSettings {
value={this.state.enableWebhookDebugging}
onChange={this.handleChange}
/>
+ <BooleanSetting
+ id='enableDiagnostics'
+ label={
+ <FormattedMessage
+ id='admin.log.enableDiagnostics'
+ defaultMessage='Enable Diagnostics and Error Reporting:'
+ />
+ }
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.log.enableDiagnosticsDescription'
+ defaultMessage='Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <a href="https://about.mattermost.com/default-privacy-policy/" target="_blank">privacy policy</a> to learn more.'
+ />
+ }
+ value={this.state.enableDiagnostics}
+ onChange={this.handleChange}
+ />
</SettingsGroup>
);
}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 78496eb77..d4894f704 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -463,6 +463,8 @@
"admin.log.consoleTitle": "Output logs to console: ",
"admin.log.enableWebhookDebugging": "Enable Webhook Debugging:",
"admin.log.enableWebhookDebuggingDescription": "You can set this to false to disable the debug logging of all incoming webhook request bodies.",
+ "admin.log.enableDiagnostics": "Enable Diagnostics and Error Reporting:",
+ "admin.log.enableDiagnosticsDescription": "Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <a href=\"https://about.mattermost.com/default-privacy-policy/\" target=\"_blank\">privacy policy</a> to learn more.",
"admin.log.fileDescription": "Typically set to true in production. When true, log files are written to the log file specified in file location field below.",
"admin.log.fileLevelDescription": "This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.",
"admin.log.fileLevelTitle": "File Log Level:",