From eb492f27d05536fe4485abf457bbddd7a57a4559 Mon Sep 17 00:00:00 2001 From: Antonio Lorusso Date: Thu, 17 Jan 2019 17:52:00 +0100 Subject: [PATCH 01/33] upgrade the deps and remove all the warnings --- lib/segment/http.ex | 7 +++---- mix.exs | 13 +++++++------ mix.lock | 24 +++++++++++++++--------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/segment/http.ex b/lib/segment/http.ex index 5568bc1..24ec5f4 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -8,19 +8,18 @@ defmodule Segment.Analytics.Http do end def post(url, body, headers, options \\ []) do - options_with_auth = - Keyword.merge(options, [hackney: [basic_auth: {Segment.write_key(), ""}]]) + options_with_auth = Keyword.merge(options, hackney: [basic_auth: {Segment.write_key(), ""}]) request(:post, url, body, headers, options_with_auth) end def process_options(options) do - Keyword.put(options, :basic_auth, {Segment.write_key(),""}) + Keyword.put(options, :basic_auth, {Segment.write_key(), ""}) end def process_request_headers(headers) do headers |> Keyword.put(:"Content-Type", "application/json") - |> Keyword.put(:"accept", "application/json") + |> Keyword.put(:accept, "application/json") end end diff --git a/mix.exs b/mix.exs index a9b52fd..8f977fd 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule AnalyticsElixir.Mixfile do elixir: "~> 1.0", deps: deps(), description: "analytics_elixir", - package: package(), + package: package() ] end @@ -30,18 +30,19 @@ defmodule AnalyticsElixir.Mixfile do # Type `mix help deps` for more examples and options defp deps do [ - {:httpoison, "~> 0.12"}, - {:poison, "~> 1.3 or ~> 2.0 or ~> 3.1.0"}, - {:ex_doc, ">= 0.0.0", only: :dev} + {:httpoison, "~> 1.4"}, + {:poison, "~> 4.0"}, + {:ex_doc, "~> 0.19", only: :dev, runtime: false} ] end defp package do - [ # These are the default files included in the package + # These are the default files included in the package + [ files: ["lib", "mix.exs", "README*", "LICENSE*"], maintainers: ["Stuart Eccles"], licenses: ["MIT"], - links: %{ "GitHub" => "https://github.com/stueccles/analytics-elixir" } + links: %{"GitHub" => "https://github.com/stueccles/analytics-elixir"} ] end end diff --git a/mix.lock b/mix.lock index 527a8ff..ad6268a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,11 +1,17 @@ -%{"certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], []}, - "earmark": {:hex, :earmark, "1.0.2", "a0b0904d74ecc14da8bd2e6e0248e1a409a2bc91aade75fcf428125603de3853", [:mix], []}, - "ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, - "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, optional: false]}, {:idna, "5.0.2", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]}, - "httpoison": {:hex, :httpoison, "0.12.0", "8fc3d791c5afe6beb0093680c667dd4ce712a49d89c38c3fe1a43100dd76cf90", [:mix], [{:hackney, "~> 1.8.0", [hex: :hackney, optional: false]}]}, - "idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, optional: false]}]}, +%{ + "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "poison": {:hex, :poison, "1.5.2", "560bdfb7449e3ddd23a096929fb9fc2122f709bcc758b2d5d5a5c7d0ea848910", [:mix], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], []}} + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, +} From 730d9f504a5d5c248ccb3e029200dfcc2cde61d5 Mon Sep 17 00:00:00 2001 From: Antonio Lorusso Date: Thu, 17 Jan 2019 19:46:05 +0100 Subject: [PATCH 02/33] formatting --- lib/segment/analytics.ex | 56 +++++++-------- lib/segment/model.ex | 145 ++++++++++++++++++--------------------- 2 files changed, 92 insertions(+), 109 deletions(-) diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index 5238c90..69413d1 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -8,11 +8,13 @@ defmodule Segment.Analytics do call(t) end - def track(user_id, event, properties \\ %{}, context \\ Context.new) do - %Segment.Analytics.Track{ userId: user_id, - event: event, - properties: properties, - context: context } + def track(user_id, event, properties \\ %{}, context \\ Context.new()) do + %Segment.Analytics.Track{ + userId: user_id, + event: event, + properties: properties, + context: context + } |> call end @@ -20,10 +22,8 @@ defmodule Segment.Analytics do call(i) end - def identify(user_id, traits \\ %{}, context \\ Context.new) do - %Segment.Analytics.Identify{ userId: user_id, - traits: traits, - context: context } + def identify(user_id, traits \\ %{}, context \\ Context.new()) do + %Segment.Analytics.Identify{userId: user_id, traits: traits, context: context} |> call end @@ -31,11 +31,13 @@ defmodule Segment.Analytics do call(s) end - def screen(user_id, name \\ "", properties \\ %{}, context \\ Context.new ) do - %Segment.Analytics.Screen{ userId: user_id, - name: name, - properties: properties, - context: context } + def screen(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) do + %Segment.Analytics.Screen{ + userId: user_id, + name: name, + properties: properties, + context: context + } |> call end @@ -43,10 +45,8 @@ defmodule Segment.Analytics do call(a) end - def alias(user_id, previous_id, context \\ Context.new) do - %Segment.Analytics.Alias{ userId: user_id, - previousId: previous_id, - context: context } + def alias(user_id, previous_id, context \\ Context.new()) do + %Segment.Analytics.Alias{userId: user_id, previousId: previous_id, context: context} |> call end @@ -54,11 +54,8 @@ defmodule Segment.Analytics do call(g) end - def group(user_id, group_id, traits \\ %{}, context \\ Context.new) do - %Segment.Analytics.Group{ userId: user_id, - groupId: group_id, - traits: traits, - context: context } + def group(user_id, group_id, traits \\ %{}, context \\ Context.new()) do + %Segment.Analytics.Group{userId: user_id, groupId: group_id, traits: traits, context: context} |> call end @@ -66,11 +63,8 @@ defmodule Segment.Analytics do call(p) end - def page(user_id, name \\ "", properties \\ %{}, context \\ Context.new ) do - %Segment.Analytics.Page{ userId: user_id, - name: name, - properties: properties, - context: context } + def page(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) do + %Segment.Analytics.Page{userId: user_id, name: name, properties: properties, context: context} |> call end @@ -84,17 +78,17 @@ defmodule Segment.Analytics do end defp log_result({_, %{status_code: code}}, function, body) when code in 200..299 do - #success + # success Logger.debug("Segment #{function} call success: #{code} with body: #{body}") end defp log_result({_, %{status_code: code}}, function, body) do - #HTTP failure + # HTTP failure Logger.debug("Segment #{function} call failed: #{code} with body: #{body}") end defp log_result(error, function, body) do - #every other failure + # every other failure Logger.debug("Segment #{function} call failed: #{inspect(error)} with body: #{body}") end end diff --git a/lib/segment/model.ex b/lib/segment/model.ex index 3d6bb73..c003cfb 100644 --- a/lib/segment/model.ex +++ b/lib/segment/model.ex @@ -2,89 +2,78 @@ defmodule Segment.Analytics.Track do @derive [Poison.Encoder] @method "track" - defstruct [ :userId, - :event, - :properties, - :context, - :timestamp, - :integrations, - :anonymousId, - method: @method - ] - + defstruct [ + :userId, + :event, + :properties, + :context, + :timestamp, + :integrations, + :anonymousId, + method: @method + ] end defmodule Segment.Analytics.Identify do @derive [Poison.Encoder] @method "identify" - defstruct [ :userId, - :traits, - :context, - :timestamp, - :integrations, - :anonymousId, - method: @method - ] + defstruct [:userId, :traits, :context, :timestamp, :integrations, :anonymousId, method: @method] end defmodule Segment.Analytics.Alias do @derive [Poison.Encoder] @method "alias" - defstruct [ :userId, - :previousId, - :context, - :timestamp, - :integrations, - method: @method - ] - + defstruct [:userId, :previousId, :context, :timestamp, :integrations, method: @method] end defmodule Segment.Analytics.Page do @derive [Poison.Encoder] @method "page" - defstruct [ :userId, - :name, - :properties, - :context, - :timestamp, - :integrations, - :anonymousId, - method: @method] - + defstruct [ + :userId, + :name, + :properties, + :context, + :timestamp, + :integrations, + :anonymousId, + method: @method + ] end defmodule Segment.Analytics.Screen do @derive [Poison.Encoder] @method "screen" - defstruct [ :userId, - :name, - :properties, - :context, - :timestamp, - :integrations, - :anonymousId, - method: @method - ] + defstruct [ + :userId, + :name, + :properties, + :context, + :timestamp, + :integrations, + :anonymousId, + method: @method + ] end defmodule Segment.Analytics.Group do @derive [Poison.Encoder] @method "group" - defstruct [ :userId, - :groupId, - :traits, - :context, - :timestamp, - :integrations, - :anonymousId, - method: @method - ] + defstruct [ + :userId, + :groupId, + :traits, + :context, + :timestamp, + :integrations, + :anonymousId, + method: @method + ] end defmodule Segment.Analytics.Context do @@ -92,29 +81,29 @@ defmodule Segment.Analytics.Context do @library_name Mix.Project.get().project[:description] @library_version Mix.Project.get().project[:version] - defstruct [ :app, - :campaign, - :device, - :ip, - :library, - :locale, - :location, - :network, - :os, - :page, - :referrer, - :screen, - :timezone, - :traits, - :userAgent - ] - - def update(context = %__MODULE__{}) do - %{context | library: %{name: @library_name, version: @library_version}} - end - - def new do - update(%__MODULE__{}) - end - + defstruct [ + :app, + :campaign, + :device, + :ip, + :library, + :locale, + :location, + :network, + :os, + :page, + :referrer, + :screen, + :timezone, + :traits, + :userAgent + ] + + def update(context = %__MODULE__{}) do + %{context | library: %{name: @library_name, version: @library_version}} + end + + def new do + update(%__MODULE__{}) + end end From e311fcf85d9431a20681212e13a1f4120af0d49c Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Mon, 21 Jan 2019 14:21:07 +0100 Subject: [PATCH 03/33] make the endpoint configurable (#1) * make the endpoint configurable * update the doc * include the Agent in the segment module --- README.md | 14 ++++++++++---- lib/segment.ex | 40 +++++++++++++++++++++++++++++++++++----- lib/segment/http.ex | 8 +++----- test/segment_test.exs | 4 +++- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2b75eda..5789bb3 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ and then `mix deps.get` ## Usage -Start the Segment agent with your write_key from Segment +Start the Segment agent with your write_key from Segment, and the endpoint. +The __endpoint__ is optional and if omitted, it defaults to `https://api.segment.io/v1/`. ``` -Segment.start_link("YOUR_WRITE_KEY") +Segment.start_link("YOUR_SEGMENT_KEY", "https://example.com/v1") ``` There are then two ways to call the different methods on the API. A basic way through `Segment.Analytics` or by passing a full Struct @@ -25,6 +26,7 @@ This is how I add to a Phoenix project (may not be your preferred way) 1. Add the following to deps section of your mix.exs: `{:segment, github: "stueccles/analytics-elixir"}` and then `mix deps.get` + 2. Add segment to applications list in the Phoenix project mix.exs ie. ``` @@ -34,16 +36,20 @@ def application do :phoenix_ecto, :postgrex, :segment]] end ``` + 3. Add a config variable for your write_key (may want to make this environment dependent) ie. ``` config :segment, - write_key: "2iFFnRsCfi" + key: "your_segment_key", + endpoint: "https://api.segment.io/v1/" ``` +The __endpoint__ is optional (as specified in the Usage section above). + 4. Start the segment agent as a child of the application in the application file under the lib directory. In the children list add: ``` -worker(Segment, [Application.get_env(:segment, :write_key)]) +{Segment, [Application.get_env(:segment, :key), Application.get_env(:segment, :endpoint)]} ``` ### Track diff --git a/lib/segment.ex b/lib/segment.ex index f988d1f..74a498a 100644 --- a/lib/segment.ex +++ b/lib/segment.ex @@ -1,14 +1,44 @@ defmodule Segment do + use Agent @type status :: :ok | :error - @spec start_link(binary) :: { Segment.status, pid } - def start_link(write_key) do - Agent.start_link(fn -> write_key end, name: __MODULE__) + @default_endpoint "https://api.segment.io/v1/" + + @spec start_link(String.t(), String.t()) :: {Segment.status(), pid} + def start_link(key, endpoint \\ @default_endpoint) do + Agent.start_link(fn -> %{endpoint: endpoint, key: key} end, name: __MODULE__) end - def write_key() do - Agent.get(__MODULE__, fn(state) -> state end) + @doc """ + Returns the segment key + + ## Examples + + iex> Segment.start_link("key") + ...> Segment.key() + "key" + + """ + def key() do + Agent.get(__MODULE__, &Map.get(&1, :key)) end + @doc """ + Returns the segment endpoint + + ## Examples + + iex> Segment.start_link("key") + ...> Segment.endpoint() + "https://api.segment.io/v1/" + + iex> Segment.start_link("key", "https://example.com") + ...> Segment.endpoint() + "https://example.com" + + """ + def endpoint() do + Agent.get(__MODULE__, &Map.get(&1, :endpoint)) + end end diff --git a/lib/segment/http.ex b/lib/segment/http.ex index 24ec5f4..d935c67 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -1,20 +1,18 @@ defmodule Segment.Analytics.Http do use HTTPoison.Base - @base_url "https://api.segment.io/v1/" - def process_url(url) do - @base_url <> url + Segment.endpoint() <> url end def post(url, body, headers, options \\ []) do - options_with_auth = Keyword.merge(options, hackney: [basic_auth: {Segment.write_key(), ""}]) + options_with_auth = Keyword.merge(options, hackney: [basic_auth: {Segment.key(), ""}]) request(:post, url, body, headers, options_with_auth) end def process_options(options) do - Keyword.put(options, :basic_auth, {Segment.write_key(), ""}) + Keyword.put(options, :basic_auth, {Segment.key(), ""}) end def process_request_headers(headers) do diff --git a/test/segment_test.exs b/test/segment_test.exs index 63781e7..c8b213e 100644 --- a/test/segment_test.exs +++ b/test/segment_test.exs @@ -1,11 +1,13 @@ defmodule SegmentTest do use ExUnit.Case + doctest Segment + @segment_test_key System.get_env("SEGMENT_KEY") test "track debugging" do Segment.start_link(@segment_test_key) - t = Segment.Analytics.track("343434", "track debugging #{elem(:os.timestamp,2)}") + t = Segment.Analytics.track("343434", "track debugging #{elem(:os.timestamp(), 2)}") Task.await(t) end end From 040b55c70a82ced0077b6baa4d9fb7dc36987483 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Wed, 23 Jan 2019 16:03:34 +0100 Subject: [PATCH 04/33] add the child specification (#2) --- lib/segment.ex | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/segment.ex b/lib/segment.ex index 74a498a..c5ef88c 100644 --- a/lib/segment.ex +++ b/lib/segment.ex @@ -10,6 +10,39 @@ defmodule Segment do Agent.start_link(fn -> %{endpoint: endpoint, key: key} end, name: __MODULE__) end + @doc """ + The child specifications + + ## Examples + + iex> Segment.child_spec([key: "something"]) + %{ + id: Segment, + start: {Segment, :start_link, ["something", nil]} + } + + iex> Segment.child_spec([]) + ** (KeyError) key :key not found in: [] + + iex> Segment.child_spec([key: "something", endpoint: "http://example.com"]) + %{ + id: Segment, + start: {Segment, :start_link, ["something", "http://example.com"]} + } + + """ + def child_spec(arg) do + opts = [ + Keyword.fetch!(arg, :key), + Keyword.get(arg, :endpoint) + ] + + %{ + id: Segment, + start: {Segment, :start_link, opts} + } + end + @doc """ Returns the segment key From 7baea4fe73043d8cf1f11a8fd29dc3c49d624329 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Fri, 25 Jan 2019 16:55:49 +0100 Subject: [PATCH 05/33] pass the authentication as header (#3) * pass the authentication as header * Delete http.ex --- lib/segment/http.ex | 9 ++------- mix.exs | 3 ++- mix.lock | 8 ++++++++ test/segment/http_test.exs | 37 +++++++++++++++++++++++++++++++++++++ test/test_helper.exs | 1 + 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 test/segment/http_test.exs diff --git a/lib/segment/http.ex b/lib/segment/http.ex index d935c67..8dfe88f 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -6,18 +6,13 @@ defmodule Segment.Analytics.Http do end def post(url, body, headers, options \\ []) do - options_with_auth = Keyword.merge(options, hackney: [basic_auth: {Segment.key(), ""}]) - - request(:post, url, body, headers, options_with_auth) - end - - def process_options(options) do - Keyword.put(options, :basic_auth, {Segment.key(), ""}) + request(:post, url, body, headers, options) end def process_request_headers(headers) do headers |> Keyword.put(:"Content-Type", "application/json") |> Keyword.put(:accept, "application/json") + |> Keyword.put(:"X-API-Key", Segment.key()) end end diff --git a/mix.exs b/mix.exs index 8f977fd..21b368c 100644 --- a/mix.exs +++ b/mix.exs @@ -32,7 +32,8 @@ defmodule AnalyticsElixir.Mixfile do [ {:httpoison, "~> 1.4"}, {:poison, "~> 4.0"}, - {:ex_doc, "~> 0.19", only: :dev, runtime: false} + {:ex_doc, "~> 0.19", only: :dev, runtime: false}, + {:bypass, "~> 1.0", only: :test} ] end diff --git a/mix.lock b/mix.lock index ad6268a..836cd16 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,8 @@ %{ + "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"}, "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, @@ -8,10 +11,15 @@ "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/test/segment/http_test.exs b/test/segment/http_test.exs new file mode 100644 index 0000000..9bb3c4e --- /dev/null +++ b/test/segment/http_test.exs @@ -0,0 +1,37 @@ +defmodule Segment.Analytics.HttpTest do + use ExUnit.Case, async: true + + @apikey "afakekey" + @url "something" + @body ~s({"sample": "body"}) + + setup do + bypass = Bypass.open() + Segment.start_link(@apikey, endpoint_url(bypass.port)) + + {:ok, bypass: bypass} + end + + describe "post/4" do + test "the request sent is correct", %{bypass: bypass} do + Bypass.expect(bypass, "POST", "/#{@url}", fn conn -> + [ + {"accept", "application/json"}, + {"content-type", "application/json"}, + {"x-api-key", @apikey} + ] + |> Enum.each(fn {header, value} -> + assert [value] == Plug.Conn.get_req_header(conn, header) + end) + + assert {:ok, @body, _conn} = Plug.Conn.read_body(conn) + + Plug.Conn.resp(conn, 200, "") + end) + + Segment.Analytics.Http.post(@url, @body, []) + end + end + + defp endpoint_url(port), do: "http://localhost:#{port}/" +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..071f140 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,2 @@ ExUnit.start() +Application.ensure_all_started(:bypass) From 0c6fd25952f4ce23bb8bc064fbf8ed24e7f959b9 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Mon, 28 Jan 2019 10:16:36 +0100 Subject: [PATCH 06/33] support the tracking of a list of events (#4) --- lib/segment/analytics.ex | 11 +++++++ lib/segment/model.ex | 10 +++++++ test/segment/analytics_test.exs | 53 +++++++++++++++++++++++++++++++++ test/segment/http_test.exs | 4 ++- 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/segment/analytics_test.exs diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index 69413d1..e457dfb 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -4,6 +4,17 @@ defmodule Segment.Analytics do require Logger + def batch_track(events) when is_list(events) do + %Segment.Analytics.BatchTrack{ + batch: Enum.map(events, fn e -> valid_track_event(e) end) + } + |> call + end + + defp valid_track_event(track = %Segment.Analytics.Track{}) do + track + end + def track(t = %Segment.Analytics.Track{}) do call(t) end diff --git a/lib/segment/model.ex b/lib/segment/model.ex index c003cfb..4aa430f 100644 --- a/lib/segment/model.ex +++ b/lib/segment/model.ex @@ -1,3 +1,13 @@ +defmodule Segment.Analytics.BatchTrack do + @derive {Poison.Encoder, except: [:method]} + @method "track" + + defstruct [ + :batch, + method: @method + ] +end + defmodule Segment.Analytics.Track do @derive [Poison.Encoder] @method "track" diff --git a/test/segment/analytics_test.exs b/test/segment/analytics_test.exs new file mode 100644 index 0000000..916d2f7 --- /dev/null +++ b/test/segment/analytics_test.exs @@ -0,0 +1,53 @@ +defmodule Segment.Analytics.AnalyticsTest do + # not used in async mode because of Bypass + # test fail randomly + use ExUnit.Case + + setup do + bypass = Bypass.open() + Segment.start_link("123", endpoint_url(bypass.port)) + + {:ok, bypass: bypass} + end + + describe "batch_track/1" do + test "send a batch event", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + body = ~s""" + { + "batch":[ + { + "userId":null, + "timestamp":null, + "properties":{}, + "method":"track", + "integrations":null, + "event":"test1", + "context":{}, + "anonymousId":null + } + ] + } + """ + + assert {:ok, body, _conn} = Plug.Conn.read_body(conn) + + Plug.Conn.resp(conn, 200, "") + end) + + events = [ + %Segment.Analytics.Track{ + userId: nil, + event: "test1", + properties: %{}, + context: %{} + } + ] + + task = Segment.Analytics.batch_track(events) + Task.await(task) + end + end + + defp endpoint_url(port), do: "http://localhost:#{port}/" +end diff --git a/test/segment/http_test.exs b/test/segment/http_test.exs index 9bb3c4e..1c03716 100644 --- a/test/segment/http_test.exs +++ b/test/segment/http_test.exs @@ -1,5 +1,7 @@ defmodule Segment.Analytics.HttpTest do - use ExUnit.Case, async: true + # not used in async mode because of Bypass + # test fail randomly + use ExUnit.Case @apikey "afakekey" @url "something" From 6607c209cab72942b2b9cbc359bd400bfc2f2023 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Mon, 28 Jan 2019 13:33:52 +0100 Subject: [PATCH 07/33] send all the requests to the root url (#5) also remove debug logging not useful for debugging for the lack of information provided --- lib/segment/analytics.ex | 8 ++------ lib/segment/http.ex | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index e457dfb..259e4d7 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -84,7 +84,8 @@ defmodule Segment.Analytics do end defp post_to_segment(function, body) do - Http.post(function, body) + # all the requests go to the root url + Http.post("", body) |> log_result(function, body) end @@ -93,11 +94,6 @@ defmodule Segment.Analytics do Logger.debug("Segment #{function} call success: #{code} with body: #{body}") end - defp log_result({_, %{status_code: code}}, function, body) do - # HTTP failure - Logger.debug("Segment #{function} call failed: #{code} with body: #{body}") - end - defp log_result(error, function, body) do # every other failure Logger.debug("Segment #{function} call failed: #{inspect(error)} with body: #{body}") diff --git a/lib/segment/http.ex b/lib/segment/http.ex index 8dfe88f..845cbef 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -13,6 +13,6 @@ defmodule Segment.Analytics.Http do headers |> Keyword.put(:"Content-Type", "application/json") |> Keyword.put(:accept, "application/json") - |> Keyword.put(:"X-API-Key", Segment.key()) + |> Keyword.put(:"x-api-key", Segment.key()) end end From c2287827c331b44f052725d8ee74eb91774efcc7 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Mon, 28 Jan 2019 20:56:13 +0100 Subject: [PATCH 08/33] add timestamps default fields and library struct (#6) --- lib/segment/analytics.ex | 114 ++++++++++++++++++-------------- lib/segment/http.ex | 4 -- lib/segment/model.ex | 30 ++++++--- mix.exs | 1 + test/segment/analytics_test.exs | 20 +++--- test/segment/model_test.exs | 17 +++++ test/segment_test.exs | 2 +- 7 files changed, 112 insertions(+), 76 deletions(-) create mode 100644 test/segment/model_test.exs diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index 259e4d7..fbc2a3e 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -4,22 +4,9 @@ defmodule Segment.Analytics do require Logger - def batch_track(events) when is_list(events) do - %Segment.Analytics.BatchTrack{ - batch: Enum.map(events, fn e -> valid_track_event(e) end) - } - |> call - end - - defp valid_track_event(track = %Segment.Analytics.Track{}) do - track - end - - def track(t = %Segment.Analytics.Track{}) do - call(t) - end + def track(t = %Segment.Analytics.Track{}), do: call(t) - def track(user_id, event, properties \\ %{}, context \\ Context.new()) do + def track(user_id, event, properties \\ %{}, context \\ %Context{}) do %Segment.Analytics.Track{ userId: user_id, event: event, @@ -29,20 +16,20 @@ defmodule Segment.Analytics do |> call end - def identify(i = %Segment.Analytics.Identify{}) do - call(i) - end + def identify(i = %Segment.Analytics.Identify{}), do: call(i) - def identify(user_id, traits \\ %{}, context \\ Context.new()) do - %Segment.Analytics.Identify{userId: user_id, traits: traits, context: context} + def identify(user_id, traits \\ %{}, context \\ %Context{}) do + %Segment.Analytics.Identify{ + userId: user_id, + traits: traits, + context: context + } |> call end - def screen(s = %Segment.Analytics.Screen{}) do - call(s) - end + def screen(s = %Segment.Analytics.Screen{}), do: call(s) - def screen(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) do + def screen(user_id, name \\ "", properties \\ %{}, context \\ %Context{}) do %Segment.Analytics.Screen{ userId: user_id, name: name, @@ -52,50 +39,79 @@ defmodule Segment.Analytics do |> call end - def alias(a = %Segment.Analytics.Alias{}) do - call(a) - end + def alias(a = %Segment.Analytics.Alias{}), do: call(a) - def alias(user_id, previous_id, context \\ Context.new()) do - %Segment.Analytics.Alias{userId: user_id, previousId: previous_id, context: context} + def alias(user_id, previous_id, context \\ %Context{}) do + %Segment.Analytics.Alias{ + userId: user_id, + previousId: previous_id, + context: context + } |> call end - def group(g = %Segment.Analytics.Group{}) do - call(g) + def group(g = %Segment.Analytics.Group{}), do: call(g) + + def group(user_id, group_id, traits \\ %{}, context \\ %Context{}) do + %Segment.Analytics.Group{ + userId: user_id, + groupId: group_id, + traits: traits, + context: context + } + |> call end - def group(user_id, group_id, traits \\ %{}, context \\ Context.new()) do - %Segment.Analytics.Group{userId: user_id, groupId: group_id, traits: traits, context: context} + def page(p = %Segment.Analytics.Page{}), do: call(p) + + def page(user_id, name \\ "", properties \\ %{}, context \\ %Context{}) do + %Segment.Analytics.Page{ + userId: user_id, + name: name, + properties: properties, + context: context + } |> call end - def page(p = %Segment.Analytics.Page{}) do - call(p) + defp call(model) do + batch = + model + |> fill_context() + |> fill_dates() + |> model_to_batch() + |> Poison.encode!() + + Task.async(fn -> post_to_segment(batch) end) end - def page(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) do - %Segment.Analytics.Page{userId: user_id, name: name, properties: properties, context: context} - |> call + # TODO: replace with an actual buffering + # to send events in batches rather than one by one + # The idea is to reduce the traffic to the segment service + defp model_to_batch(model) do + %{batch: [model]} + end + + defp fill_context(model) do + put_in(model.context.library, Segment.Analytics.Context.Library.build()) end - defp call(api) do - Task.async(fn -> post_to_segment(api.method, Poison.encode!(api)) end) + defp fill_dates(model) do + put_in(model.sentAt, :os.system_time(:milli_seconds)) end - defp post_to_segment(function, body) do - # all the requests go to the root url + defp post_to_segment(body) do Http.post("", body) - |> log_result(function, body) + |> log_result(body) end - defp log_result({_, %{status_code: code}}, function, body) when code in 200..299 do - # success - Logger.debug("Segment #{function} call success: #{code} with body: #{body}") + # log success responses + defp log_result({_, %{status_code: code}}, body) when code in 200..299 do + Logger.debug("[#{__MODULE__}] call success: #{code} with body: #{body}") end - defp log_result(error, function, body) do - # every other failure - Logger.debug("Segment #{function} call failed: #{inspect(error)} with body: #{body}") + # log failed responses + defp log_result(error, body) do + Logger.debug("[#{__MODULE__}] call failed: #{inspect(error)} with body: #{body}") end end diff --git a/lib/segment/http.ex b/lib/segment/http.ex index 845cbef..7836dcf 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -5,10 +5,6 @@ defmodule Segment.Analytics.Http do Segment.endpoint() <> url end - def post(url, body, headers, options \\ []) do - request(:post, url, body, headers, options) - end - def process_request_headers(headers) do headers |> Keyword.put(:"Content-Type", "application/json") diff --git a/lib/segment/model.ex b/lib/segment/model.ex index 4aa430f..248a600 100644 --- a/lib/segment/model.ex +++ b/lib/segment/model.ex @@ -20,6 +20,7 @@ defmodule Segment.Analytics.Track do :timestamp, :integrations, :anonymousId, + :sentAt, method: @method ] end @@ -86,11 +87,26 @@ defmodule Segment.Analytics.Group do ] end -defmodule Segment.Analytics.Context do +defmodule Segment.Analytics.Context.Library do @derive [Poison.Encoder] - @library_name Mix.Project.get().project[:description] - @library_version Mix.Project.get().project[:version] + @project_name Mix.Project.get().project[:name] + @project_version Mix.Project.get().project[:version] + + defstruct [:name, :version, :transport] + + def build() do + %__MODULE__{ + name: @project_name, + version: @project_version, + # the only supported by the library for now. + transport: "http" + } + end +end + +defmodule Segment.Analytics.Context do + @derive [Poison.Encoder] defstruct [ :app, :campaign, @@ -108,12 +124,4 @@ defmodule Segment.Analytics.Context do :traits, :userAgent ] - - def update(context = %__MODULE__{}) do - %{context | library: %{name: @library_name, version: @library_version}} - end - - def new do - update(%__MODULE__{}) - end end diff --git a/mix.exs b/mix.exs index 21b368c..d57e1ad 100644 --- a/mix.exs +++ b/mix.exs @@ -7,6 +7,7 @@ defmodule AnalyticsElixir.Mixfile do version: "0.1.2", elixir: "~> 1.0", deps: deps(), + name: "analytics_elixir", description: "analytics_elixir", package: package() ] diff --git a/test/segment/analytics_test.exs b/test/segment/analytics_test.exs index 916d2f7..3f517e7 100644 --- a/test/segment/analytics_test.exs +++ b/test/segment/analytics_test.exs @@ -10,8 +10,8 @@ defmodule Segment.Analytics.AnalyticsTest do {:ok, bypass: bypass} end - describe "batch_track/1" do - test "send a batch event", %{bypass: bypass} do + describe "track/1" do + test "send a track event", %{bypass: bypass} do Bypass.expect(bypass, fn conn -> body = ~s""" { @@ -35,16 +35,14 @@ defmodule Segment.Analytics.AnalyticsTest do Plug.Conn.resp(conn, 200, "") end) - events = [ - %Segment.Analytics.Track{ - userId: nil, - event: "test1", - properties: %{}, - context: %{} - } - ] + event = %Segment.Analytics.Track{ + userId: nil, + event: "test1", + properties: %{}, + context: %Segment.Analytics.Context{} + } - task = Segment.Analytics.batch_track(events) + task = Segment.Analytics.track(event) Task.await(task) end end diff --git a/test/segment/model_test.exs b/test/segment/model_test.exs new file mode 100644 index 0000000..9faa104 --- /dev/null +++ b/test/segment/model_test.exs @@ -0,0 +1,17 @@ +defmodule Segment.Analytics.Context.LibraryTest do + # not used in async mode because of Bypass + # test fail randomly + use ExUnit.Case + + alias Segment.Analytics.Context.Library + + describe "build/1" do + test "builds the library struct with the default values" do + assert %Library{ + name: Mix.Project.get().project[:name], + transport: "http", + version: Mix.Project.get().project[:version] + } == Library.build() + end + end +end diff --git a/test/segment_test.exs b/test/segment_test.exs index c8b213e..ce0a3b6 100644 --- a/test/segment_test.exs +++ b/test/segment_test.exs @@ -6,7 +6,7 @@ defmodule SegmentTest do @segment_test_key System.get_env("SEGMENT_KEY") test "track debugging" do - Segment.start_link(@segment_test_key) + Segment.start_link(@segment_test_key, "https://api.segment.io/v1/track") t = Segment.Analytics.track("343434", "track debugging #{elem(:os.timestamp(), 2)}") Task.await(t) end From e99549eb7e084857a5ba25f600c77075e745c8e8 Mon Sep 17 00:00:00 2001 From: antonio lorusso Date: Tue, 29 Jan 2019 12:47:30 +0100 Subject: [PATCH 09/33] fix the event message structure (#7) --- lib/segment/analytics.ex | 15 +++++---- lib/segment/model.ex | 34 +++++++++++++------- mix.exs | 7 +++-- mix.lock | 1 + test/segment/analytics_test.exs | 56 ++++++++++++++++++++++----------- 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index fbc2a3e..51e3081 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -77,9 +77,9 @@ defmodule Segment.Analytics do defp call(model) do batch = model + |> generate_message_id() |> fill_context() - |> fill_dates() - |> model_to_batch() + |> wrap_in_batch() |> Poison.encode!() Task.async(fn -> post_to_segment(batch) end) @@ -88,16 +88,19 @@ defmodule Segment.Analytics do # TODO: replace with an actual buffering # to send events in batches rather than one by one # The idea is to reduce the traffic to the segment service - defp model_to_batch(model) do - %{batch: [model]} + defp wrap_in_batch(model) do + %Segment.Analytics.Batch{ + batch: [model], + sentAt: :os.system_time(:milli_seconds) + } end defp fill_context(model) do put_in(model.context.library, Segment.Analytics.Context.Library.build()) end - defp fill_dates(model) do - put_in(model.sentAt, :os.system_time(:milli_seconds)) + defp generate_message_id(model) do + put_in(model.messageId, UUID.uuid4()) end defp post_to_segment(body) do diff --git a/lib/segment/model.ex b/lib/segment/model.ex index 248a600..2a5f687 100644 --- a/lib/segment/model.ex +++ b/lib/segment/model.ex @@ -1,10 +1,9 @@ -defmodule Segment.Analytics.BatchTrack do - @derive {Poison.Encoder, except: [:method]} - @method "track" +defmodule Segment.Analytics.Batch do + @derive [Poison.Encoder] defstruct [ :batch, - method: @method + :sentAt ] end @@ -20,8 +19,8 @@ defmodule Segment.Analytics.Track do :timestamp, :integrations, :anonymousId, - :sentAt, - method: @method + :messageId, + type: @method ] end @@ -29,14 +28,23 @@ defmodule Segment.Analytics.Identify do @derive [Poison.Encoder] @method "identify" - defstruct [:userId, :traits, :context, :timestamp, :integrations, :anonymousId, method: @method] + defstruct [ + :userId, + :traits, + :context, + :timestamp, + :integrations, + :anonymousId, + :messageId, + type: @method + ] end defmodule Segment.Analytics.Alias do @derive [Poison.Encoder] @method "alias" - defstruct [:userId, :previousId, :context, :timestamp, :integrations, method: @method] + defstruct [:userId, :previousId, :context, :timestamp, :integrations, type: @method] end defmodule Segment.Analytics.Page do @@ -51,7 +59,8 @@ defmodule Segment.Analytics.Page do :timestamp, :integrations, :anonymousId, - method: @method + :messageId, + type: @method ] end @@ -67,7 +76,8 @@ defmodule Segment.Analytics.Screen do :timestamp, :integrations, :anonymousId, - method: @method + :messageId, + type: @method ] end @@ -83,7 +93,8 @@ defmodule Segment.Analytics.Group do :timestamp, :integrations, :anonymousId, - method: @method + :messageId, + type: @method ] end @@ -107,6 +118,7 @@ end defmodule Segment.Analytics.Context do @derive [Poison.Encoder] + defstruct [ :app, :campaign, diff --git a/mix.exs b/mix.exs index d57e1ad..e527cab 100644 --- a/mix.exs +++ b/mix.exs @@ -34,7 +34,8 @@ defmodule AnalyticsElixir.Mixfile do {:httpoison, "~> 1.4"}, {:poison, "~> 4.0"}, {:ex_doc, "~> 0.19", only: :dev, runtime: false}, - {:bypass, "~> 1.0", only: :test} + {:bypass, "~> 1.0", only: :test}, + {:uuid, "~> 1.1"} ] end @@ -42,9 +43,9 @@ defmodule AnalyticsElixir.Mixfile do # These are the default files included in the package [ files: ["lib", "mix.exs", "README*", "LICENSE*"], - maintainers: ["Stuart Eccles"], + maintainers: ["Antonio Lorusso", "Fernando Hamasaki de Amorim"], licenses: ["MIT"], - links: %{"GitHub" => "https://github.com/stueccles/analytics-elixir"} + links: %{"GitHub" => "https://github.com/FindHotel/analytics-elixir"} ] end end diff --git a/mix.lock b/mix.lock index 836cd16..e5a05f5 100644 --- a/mix.lock +++ b/mix.lock @@ -22,4 +22,5 @@ "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, } diff --git a/test/segment/analytics_test.exs b/test/segment/analytics_test.exs index 3f517e7..7a59a56 100644 --- a/test/segment/analytics_test.exs +++ b/test/segment/analytics_test.exs @@ -13,24 +13,44 @@ defmodule Segment.Analytics.AnalyticsTest do describe "track/1" do test "send a track event", %{bypass: bypass} do Bypass.expect(bypass, fn conn -> - body = ~s""" - { - "batch":[ - { - "userId":null, - "timestamp":null, - "properties":{}, - "method":"track", - "integrations":null, - "event":"test1", - "context":{}, - "anonymousId":null - } - ] - } - """ - - assert {:ok, body, _conn} = Plug.Conn.read_body(conn) + {:ok, received_body, _conn} = Plug.Conn.read_body(conn) + + assert %{ + "batch" => [ + %{ + "userId" => nil, + "type" => "track", + "timestamp" => nil, + "properties" => %{}, + "integrations" => nil, + "event" => "test1", + "context" => %{ + "userAgent" => nil, + "traits" => nil, + "timezone" => nil, + "screen" => nil, + "referrer" => nil, + "page" => nil, + "os" => nil, + "network" => nil, + "location" => nil, + "locale" => nil, + "library" => %{ + "version" => "0.1.2", + "transport" => "http", + "name" => "analytics_elixir" + }, + "ip" => nil, + "device" => nil, + "campaign" => nil, + "app" => nil + }, + "anonymousId" => nil + } + ] + } = Poison.decode!(received_body) + + # messageId and sentAt are not asserted Plug.Conn.resp(conn, 200, "") end) From f8dcad63d183fb301c1e226f0a016628d3a3d203 Mon Sep 17 00:00:00 2001 From: Antonio Lorusso Date: Tue, 29 Jan 2019 14:43:59 +0100 Subject: [PATCH 10/33] add the uuid dep --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index e527cab..d9b2ca7 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule AnalyticsElixir.Mixfile do # # Type `mix help compile.app` for more information def application do - [applications: [:httpoison, :logger, :poison]] + [applications: [:httpoison, :logger, :poison, :uuid]] end # Dependencies can be Hex packages: From f73c6d65d4fd7cfaf88dc78db7d1e0b49a4e3bdf Mon Sep 17 00:00:00 2001 From: Felipe Vieira Date: Tue, 22 Sep 2020 18:05:22 +0200 Subject: [PATCH 11/33] Allow endpoint and api key to be passed via options to the new public (#8) --- README.md | 18 ++++- lib/segment/analytics.ex | 8 +- lib/segment/http.ex | 25 ++++--- test/segment/analytics_test.exs | 126 +++++++++++++++++++++++++++++++- test/segment/http_test.exs | 35 ++++++++- test/segment_test.exs | 35 ++++++++- 6 files changed, 226 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 5789bb3..0a42617 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ analytics-elixir is a non-supported third-party client for [Segment](https://seg ## Install -Add the following to deps section of your mix.exs: `{:segment, github: "stueccles/analytics-elixir"}` +Add the following to deps section of your mix.exs: `{:segment, github: "FindHotel/analytics-elixir"}` and then `mix deps.get` @@ -24,7 +24,7 @@ with all the data for the API (allowing Context and Integrations to be set) This is how I add to a Phoenix project (may not be your preferred way) -1. Add the following to deps section of your mix.exs: `{:segment, github: "stueccles/analytics-elixir"}` +1. Add the following to deps section of your mix.exs: `{:segment, github: "FindHotel/analytics-elixir"}` and then `mix deps.get` 2. Add segment to applications list in the Phoenix project mix.exs @@ -125,6 +125,20 @@ or the full way using a struct with all the possible options for the page call |> Segment.Analytics.page ``` +### Config as options + +You can also pass the __endpoint__ and __key__ as options to the +`Segment.Analytics.call/2` along with the struct. +``` +%Segment.Analytics.Track{ userId: "sdsds", + event: "eventname", + properties: %{property1: "", property2: ""} + } + |> Segment.Analytics.call([key: "YOUR_SEGMENT_KEY", endpoint: "https://example.com/v1"]) +``` + +With this approach the options take precedence over configurations stored in the Segment agent. + ## Running tests There are not many tests at the moment. But you can run a live test on your segment diff --git a/lib/segment/analytics.ex b/lib/segment/analytics.ex index 51e3081..fcfc390 100644 --- a/lib/segment/analytics.ex +++ b/lib/segment/analytics.ex @@ -74,7 +74,7 @@ defmodule Segment.Analytics do |> call end - defp call(model) do + def call(model, options \\ []) do batch = model |> generate_message_id() @@ -82,7 +82,7 @@ defmodule Segment.Analytics do |> wrap_in_batch() |> Poison.encode!() - Task.async(fn -> post_to_segment(batch) end) + Task.async(fn -> post_to_segment(batch, options) end) end # TODO: replace with an actual buffering @@ -103,8 +103,8 @@ defmodule Segment.Analytics do put_in(model.messageId, UUID.uuid4()) end - defp post_to_segment(body) do - Http.post("", body) + defp post_to_segment(body, options) do + Http.post("", body, options) |> log_result(body) end diff --git a/lib/segment/http.ex b/lib/segment/http.ex index 7836dcf..efddceb 100644 --- a/lib/segment/http.ex +++ b/lib/segment/http.ex @@ -1,14 +1,21 @@ defmodule Segment.Analytics.Http do - use HTTPoison.Base - - def process_url(url) do - Segment.endpoint() <> url + def post(path, body, options) do + path + |> process_url(options) + |> HTTPoison.post(body, process_request_headers(options)) end - def process_request_headers(headers) do - headers - |> Keyword.put(:"Content-Type", "application/json") - |> Keyword.put(:accept, "application/json") - |> Keyword.put(:"x-api-key", Segment.key()) + def process_url(path, options), + do: get_config(options, :endpoint, &Segment.endpoint/0) <> path + + def process_request_headers(options) do + [ + {"Content-Type", "application/json"}, + {"Accept", "application/json"}, + {"x-api-key", get_config(options, :key, &Segment.key/0)} + ] end + + def get_config(options, key, default_func), + do: Keyword.get(options, key) || default_func.() end diff --git a/test/segment/analytics_test.exs b/test/segment/analytics_test.exs index 7a59a56..236a567 100644 --- a/test/segment/analytics_test.exs +++ b/test/segment/analytics_test.exs @@ -5,13 +5,13 @@ defmodule Segment.Analytics.AnalyticsTest do setup do bypass = Bypass.open() - Segment.start_link("123", endpoint_url(bypass.port)) + start_supervised({Segment, [key: "123", endpoint: endpoint_url(bypass.port)]}) {:ok, bypass: bypass} end describe "track/1" do - test "send a track event", %{bypass: bypass} do + test "sends a track event", %{bypass: bypass} do Bypass.expect(bypass, fn conn -> {:ok, received_body, _conn} = Plug.Conn.read_body(conn) @@ -67,5 +67,127 @@ defmodule Segment.Analytics.AnalyticsTest do end end + describe "call/2" do + test "sends an event", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + {:ok, received_body, _conn} = Plug.Conn.read_body(conn) + + assert %{ + "batch" => [ + %{ + "userId" => nil, + "type" => "track", + "timestamp" => nil, + "properties" => %{}, + "integrations" => nil, + "event" => "test1", + "context" => %{ + "userAgent" => nil, + "traits" => nil, + "timezone" => nil, + "screen" => nil, + "referrer" => nil, + "page" => nil, + "os" => nil, + "network" => nil, + "location" => nil, + "locale" => nil, + "library" => %{ + "version" => "0.1.2", + "transport" => "http", + "name" => "analytics_elixir" + }, + "ip" => nil, + "device" => nil, + "campaign" => nil, + "app" => nil + }, + "anonymousId" => nil + } + ] + } = Poison.decode!(received_body) + + # messageId and sentAt are not asserted + + Plug.Conn.resp(conn, 200, "") + end) + + event = %Segment.Analytics.Track{ + userId: nil, + event: "test1", + properties: %{}, + context: %Segment.Analytics.Context{} + } + + task = Segment.Analytics.call(event) + Task.await(task) + end + + test "sends an event using endpoint and key from options", %{bypass: bypass} do + Bypass.expect(bypass, fn _conn -> + flunk("#{endpoint_url(bypass.port)} shouldn't be called") + end) + + Bypass.pass(bypass) + + another_bypass = Bypass.open() + + Bypass.expect(another_bypass, fn conn -> + {:ok, received_body, _conn} = Plug.Conn.read_body(conn) + + assert %{ + "batch" => [ + %{ + "userId" => nil, + "type" => "track", + "timestamp" => nil, + "properties" => %{}, + "integrations" => nil, + "event" => "test1", + "context" => %{ + "userAgent" => nil, + "traits" => nil, + "timezone" => nil, + "screen" => nil, + "referrer" => nil, + "page" => nil, + "os" => nil, + "network" => nil, + "location" => nil, + "locale" => nil, + "library" => %{ + "version" => "0.1.2", + "transport" => "http", + "name" => "analytics_elixir" + }, + "ip" => nil, + "device" => nil, + "campaign" => nil, + "app" => nil + }, + "anonymousId" => nil + } + ] + } = Poison.decode!(received_body) + + # messageId and sentAt are not asserted + + Plug.Conn.resp(conn, 200, "") + end) + + event = %Segment.Analytics.Track{ + userId: nil, + event: "test1", + properties: %{}, + context: %Segment.Analytics.Context{} + } + + options = [key: "anotherkey", endpoint: endpoint_url(another_bypass.port)] + + task = Segment.Analytics.call(event, options) + Task.await(task) + end + end + defp endpoint_url(port), do: "http://localhost:#{port}/" end diff --git a/test/segment/http_test.exs b/test/segment/http_test.exs index 1c03716..545544e 100644 --- a/test/segment/http_test.exs +++ b/test/segment/http_test.exs @@ -9,7 +9,7 @@ defmodule Segment.Analytics.HttpTest do setup do bypass = Bypass.open() - Segment.start_link(@apikey, endpoint_url(bypass.port)) + start_supervised!({Segment, [key: @apikey, endpoint: endpoint_url(bypass.port)]}) {:ok, bypass: bypass} end @@ -33,7 +33,38 @@ defmodule Segment.Analytics.HttpTest do Segment.Analytics.Http.post(@url, @body, []) end + + test "when endpoint and key are given via options, " <> + "sends the request to the correct endpoint", + %{bypass: bypass} do + Bypass.expect(bypass, "POST", "/#{@url}", fn _conn -> + flunk("#{endpoint_url(bypass.port)} shouldn't be called") + end) + + Bypass.pass(bypass) + + another_bypass = Bypass.open() + another_apikey = "foobarbaz" + options = [key: another_apikey, endpoint: endpoint_url(another_bypass.port)] + + Bypass.expect(another_bypass, "POST", "/#{@url}", fn conn -> + [ + {"accept", "application/json"}, + {"content-type", "application/json"}, + {"x-api-key", another_apikey} + ] + |> Enum.each(fn {header, value} -> + assert [value] == Plug.Conn.get_req_header(conn, header) + end) + + assert {:ok, @body, _conn} = Plug.Conn.read_body(conn) + + Plug.Conn.resp(conn, 200, "") + end) + + Segment.Analytics.Http.post(@url, @body, options) + end end - defp endpoint_url(port), do: "http://localhost:#{port}/" + def endpoint_url(port), do: "http://localhost:#{port}/" end diff --git a/test/segment_test.exs b/test/segment_test.exs index ce0a3b6..c360753 100644 --- a/test/segment_test.exs +++ b/test/segment_test.exs @@ -5,9 +5,40 @@ defmodule SegmentTest do @segment_test_key System.get_env("SEGMENT_KEY") - test "track debugging" do - Segment.start_link(@segment_test_key, "https://api.segment.io/v1/track") + setup do + {:ok, config: [key: @segment_test_key, endpoint: "https://api.segment.io/v1/track"]} + end + + test "tracks debugging", %{config: config} do + start_supervised!({Segment, config}) + t = Segment.Analytics.track("343434", "track debugging #{elem(:os.timestamp(), 2)}") Task.await(t) end + + describe "key/0" do + test "returns segment key", %{config: config} do + start_supervised!({Segment, config}) + + assert Segment.key() == Keyword.get(config, :key) + end + + test "when agent was not started, raises an error" do + assert {:noproc, {GenServer, :call, [Segment, {:get, _function}, 5000]}} = + catch_exit(Segment.key()) + end + end + + describe "endpoint/0" do + test "returns segment endpoint", %{config: config} do + start_supervised!({Segment, config}) + + assert Segment.endpoint() == Keyword.get(config, :endpoint) + end + + test "when agent was not started, returns nil" do + assert {:noproc, {GenServer, :call, [Segment, {:get, _function}, 5000]}} = + catch_exit(Segment.endpoint()) + end + end end From cd964ac555fb8a076a8629e3b1484616ddab6590 Mon Sep 17 00:00:00 2001 From: Felipe Vieira Date: Wed, 23 Sep 2020 09:41:55 +0200 Subject: [PATCH 12/33] Bump to 0.2.0 --- mix.exs | 9 +++++++-- test/segment/analytics_test.exs | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index d9b2ca7..97c5761 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule AnalyticsElixir.Mixfile do def project do [ app: :segment, - version: "0.1.2", + version: "0.2.0", elixir: "~> 1.0", deps: deps(), name: "analytics_elixir", @@ -43,7 +43,12 @@ defmodule AnalyticsElixir.Mixfile do # These are the default files included in the package [ files: ["lib", "mix.exs", "README*", "LICENSE*"], - maintainers: ["Antonio Lorusso", "Fernando Hamasaki de Amorim"], + maintainers: [ + "Antonio Lorusso", + "Felipe Vieira", + "Fernando Hamasaki de Amorim", + "Sergio Rodrigues" + ], licenses: ["MIT"], links: %{"GitHub" => "https://github.com/FindHotel/analytics-elixir"} ] diff --git a/test/segment/analytics_test.exs b/test/segment/analytics_test.exs index 236a567..4653089 100644 --- a/test/segment/analytics_test.exs +++ b/test/segment/analytics_test.exs @@ -36,7 +36,7 @@ defmodule Segment.Analytics.AnalyticsTest do "location" => nil, "locale" => nil, "library" => %{ - "version" => "0.1.2", + "version" => "0.2.0", "transport" => "http", "name" => "analytics_elixir" }, @@ -93,7 +93,7 @@ defmodule Segment.Analytics.AnalyticsTest do "location" => nil, "locale" => nil, "library" => %{ - "version" => "0.1.2", + "version" => "0.2.0", "transport" => "http", "name" => "analytics_elixir" }, @@ -156,7 +156,7 @@ defmodule Segment.Analytics.AnalyticsTest do "location" => nil, "locale" => nil, "library" => %{ - "version" => "0.1.2", + "version" => "0.2.0", "transport" => "http", "name" => "analytics_elixir" }, From 122aef69667fa86416904ab140ede488af01ab0b Mon Sep 17 00:00:00 2001 From: Felipe Vieira Date: Mon, 28 Sep 2020 15:37:39 +0200 Subject: [PATCH 13/33] Removes unused fields (#9) * Removes unused fields * Add github action to build and run tests * Remove built docs from repo --- .formatter.exs | 4 + .github/workflows/elixir.yml | 40 ++ CHANGELOG.md | 23 + Makefile | 73 ++ README.md | 11 +- doc/.build | 20 - doc/.keep | 0 doc/404.html | 89 --- doc/Segment.Analytics.Alias.html | 100 --- doc/Segment.Analytics.Context.html | 176 ----- doc/Segment.Analytics.Group.html | 100 --- doc/Segment.Analytics.Http.html | 1000 --------------------------- doc/Segment.Analytics.Identify.html | 100 --- doc/Segment.Analytics.Page.html | 100 --- doc/Segment.Analytics.Screen.html | 100 --- doc/Segment.Analytics.Track.html | 100 --- doc/Segment.Analytics.html | 412 ----------- doc/Segment.html | 228 ------ doc/api-reference.html | 138 ---- doc/dist/app-091c05798a.css | 1 - doc/dist/app-574613960f.js | 6 - doc/dist/sidebar_items.js | 1 - doc/fonts/icomoon.eot | Bin 2472 -> 0 bytes doc/fonts/icomoon.svg | 16 - doc/fonts/icomoon.ttf | Bin 2308 -> 0 bytes doc/fonts/icomoon.woff | Bin 2384 -> 0 bytes doc/index.html | 11 - lib/segment/model.ex | 11 +- mix.exs | 27 +- test/segment/analytics_test.exs | 95 ++- 30 files changed, 208 insertions(+), 2774 deletions(-) create mode 100644 .formatter.exs create mode 100644 .github/workflows/elixir.yml create mode 100644 CHANGELOG.md create mode 100644 Makefile delete mode 100644 doc/.build create mode 100644 doc/.keep delete mode 100644 doc/404.html delete mode 100644 doc/Segment.Analytics.Alias.html delete mode 100644 doc/Segment.Analytics.Context.html delete mode 100644 doc/Segment.Analytics.Group.html delete mode 100644 doc/Segment.Analytics.Http.html delete mode 100644 doc/Segment.Analytics.Identify.html delete mode 100644 doc/Segment.Analytics.Page.html delete mode 100644 doc/Segment.Analytics.Screen.html delete mode 100644 doc/Segment.Analytics.Track.html delete mode 100644 doc/Segment.Analytics.html delete mode 100644 doc/Segment.html delete mode 100644 doc/api-reference.html delete mode 100644 doc/dist/app-091c05798a.css delete mode 100644 doc/dist/app-574613960f.js delete mode 100644 doc/dist/sidebar_items.js delete mode 100644 doc/fonts/icomoon.eot delete mode 100644 doc/fonts/icomoon.svg delete mode 100644 doc/fonts/icomoon.ttf delete mode 100644 doc/fonts/icomoon.woff delete mode 100644 doc/index.html diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml new file mode 100644 index 0000000..5060c0c --- /dev/null +++ b/.github/workflows/elixir.yml @@ -0,0 +1,40 @@ +name: analytics-elixir + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + +env: + MIX_ENV: test + +jobs: + build: + name: Build and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Elixir + uses: actions/setup-elixir@v1 + with: + elixir-version: '1.10.4' + otp-version: '23.0.4' + + - name: Restore dependencies cache + uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix- + + - name: Install dependencies + run: mix deps.get + + - name: Run tests + run: mix test + + - name: Check formatted + run: mix format --check-formatted diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5fe94df --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Removed +- Removes unused fields. + +## [0.2.0] - 2020-09-23 +### Added +- Allow endpoint and API key to be passed via options to the new public function +`Segment.Analytics.call/2`. + +## [v0.1.1] - 2016-10-13 +### Added +- First release. + +[Unreleased]: https://github.com/FindHotel/analytics-elixir/compare/0.2.0...master +[0.2.0]: https://github.com/FindHotel/analytics-elixir/compare/v0.1.1...0.2.0 +[v0.1.1]: https://github.com/FindHotel/analytics-elixir/releases/tag/v0.1.1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36b2e2f --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +# ------------------------------------------- +# Common variables +# ------------------------------------------- + +VERSION_FILE := mix.exs +VERSION := $(shell sed -En "s/^.*@version \"([0-9]*\\.[0-9]*\\.[0-9]*)\"*/\\1/p" ${VERSION_FILE}) + +BLUE_COLOR := \033[0;34m +DEFAULT_COLOR := \033[0;39m +DIM_COLOR := \033[0;2m +YELLOW_COLOR := \033[0;33m + +# ------------------------------------------- +# Default task +# ------------------------------------------- + +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Print this help + @printf "Segment Analytics Elixir ${VERSION}\n" + @awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "${BLUE_COLOR}%-30s${DEFAULT_COLOR} %s\n", $$1, $$NF }' $(MAKEFILE_LIST) + +# ------------------------------------------- +# Release task +# ------------------------------------------- + +CHANGELOG_FILE := CHANGELOG.md +DATE := $(shell date +"%Y-%m-%d") +MAJOR := $(shell echo "${VERSION}" | cut -d . -f1) +MINOR := $(shell echo "${VERSION}" | cut -d . -f2) +PATCH := $(shell echo "${VERSION}" | cut -d . -f3) +REPO_NAME := analytics-elixir +REPO := https:\/\/round-lake.dustinice.workers.dev:443\/https\/github.com\/FindHotel\/${REPO_NAME}\/compare + +.PHONY: release +release: ## Bump the version and create a new tag + @printf "${BLUE_COLOR}The current version is:${DEFAULT_COLOR} ${VERSION}\n" && \ + read -r -p "Do you want to release a [major|minor|patch]: " TYPE && \ + case "$$TYPE" in \ + "major") \ + MAJOR=$$((${MAJOR}+1)); \ + MINOR="0"; \ + PATCH="0"; \ + NEW_VERSION="$$MAJOR.$$MINOR.$$PATCH" \ + ;; \ + "minor") \ + MINOR=$$((${MINOR}+1)); \ + PATCH="0" && \ + NEW_VERSION="${MAJOR}.$$MINOR.$$PATCH" \ + ;; \ + "patch") \ + PATCH=$$((${PATCH}+1)); \ + NEW_VERSION="${MAJOR}.${MINOR}.$$PATCH" \ + ;; \ + *) \ + printf "\\n${YELLOW_COLOR}Release canceled!\n"; \ + exit 0 \ + ;; \ + esac && \ + printf "${BLUE_COLOR}The new version is:${DEFAULT_COLOR} $$NEW_VERSION\n" && \ + printf "\t${DIM_COLOR}Updating ${VERSION_FILE} version${DEFAULT_COLOR}\n" && \ + perl -p -i -e "s/@version \"${VERSION}\"/@version \"$$NEW_VERSION\"/g" ${VERSION_FILE} && \ + printf "\t${DIM_COLOR}Updating ${CHANGELOG_FILE} version${DEFAULT_COLOR}\n" && \ + perl -p -i -e "s/## \[Unreleased\]/## \[Unreleased\]\\n\\n## \[$$NEW_VERSION\] - ${DATE}/g" ${CHANGELOG_FILE} && \ + perl -p -i -e "s/${REPO}\/${VERSION}...master/${REPO}\/$$NEW_VERSION...master/g" ${CHANGELOG_FILE} && \ + perl -p -i -e "s/...master/...master\\n\[$$NEW_VERSION\]: ${REPO}\/${VERSION}...$$NEW_VERSION/g" ${CHANGELOG_FILE} && \ + printf "\t${DIM_COLOR}Recording changes to the repository${DEFAULT_COLOR}\n" && \ + git add ${VERSION_FILE} ${CHANGELOG_FILE} && \ + git commit -m "Bump to $$NEW_VERSION" > /dev/null && \ + printf "\t${DIM_COLOR}Creating release tag${DEFAULT_COLOR}\n" && \ + git tag -a -m "" $$NEW_VERSION && \ + printf "\n${BLUE_COLOR}If everything's ok, push the changes to updstream!${DEFAULT_COLOR}\n" diff --git a/README.md b/README.md index 0a42617..d5ba01b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -analytics-elixir +analytics-elixir ![analytics-elixir](https://github.com/FindHotel/analytics-elixir/workflows/analytics-elixir/badge.svg?branch=master) ================ analytics-elixir is a non-supported third-party client for [Segment](https://segment.com) @@ -146,3 +146,12 @@ account by running. ``` SEGMENT_KEY=yourkey mix test ``` + +## Release + +After merge a new feature/bug you can bump the version and push it to upstream: + +```sh +make release +git push origin master && git push origin --tags +``` diff --git a/doc/.build b/doc/.build deleted file mode 100644 index 422718d..0000000 --- a/doc/.build +++ /dev/null @@ -1,20 +0,0 @@ -/Users/stueccles/Development/elixir/analytics-elixir/doc/dist/app-091c05798a.css -/Users/stueccles/Development/elixir/analytics-elixir/doc/dist/app-574613960f.js -/Users/stueccles/Development/elixir/analytics-elixir/doc/fonts/icomoon.eot -/Users/stueccles/Development/elixir/analytics-elixir/doc/fonts/icomoon.svg -/Users/stueccles/Development/elixir/analytics-elixir/doc/fonts/icomoon.ttf -/Users/stueccles/Development/elixir/analytics-elixir/doc/fonts/icomoon.woff -index.html -api-reference.html -404.html -dist/sidebar_items.js -Segment.html -Segment.Analytics.html -Segment.Analytics.Alias.html -Segment.Analytics.Context.html -Segment.Analytics.Group.html -Segment.Analytics.Http.html -Segment.Analytics.Identify.html -Segment.Analytics.Page.html -Segment.Analytics.Screen.html -Segment.Analytics.Track.html diff --git a/doc/.keep b/doc/.keep new file mode 100644 index 0000000..e69de29 diff --git a/doc/404.html b/doc/404.html deleted file mode 100644 index 16b3ebc..0000000 --- a/doc/404.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - 404 – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

Page not found

- -

Sorry, but the page you were trying to get to, does not exist. You -may want to try searching this site using the sidebar or using our -API Reference page to find what -you were looking for.

- - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Alias.html b/doc/Segment.Analytics.Alias.html deleted file mode 100644 index f2737f9..0000000 --- a/doc/Segment.Analytics.Alias.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Alias – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Alias - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Context.html b/doc/Segment.Analytics.Context.html deleted file mode 100644 index 5357dcd..0000000 --- a/doc/Segment.Analytics.Context.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - Segment.Analytics.Context – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Context - - -

- - - - -
-

- - - - Summary -

- - - -
-

- Functions -

-
-
- new() -
- -
-
- - -
- -
- - - - - - -
- - - - - -
-

- - - - Functions -

-
- -
- - - - new() - - - -
-
- -
-
-
- -
- - - - update(context) - - - -
-
- -
-
- -
- - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Group.html b/doc/Segment.Analytics.Group.html deleted file mode 100644 index 4d8a07a..0000000 --- a/doc/Segment.Analytics.Group.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Group – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Group - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Http.html b/doc/Segment.Analytics.Http.html deleted file mode 100644 index 876ebee..0000000 --- a/doc/Segment.Analytics.Http.html +++ /dev/null @@ -1,1000 +0,0 @@ - - - - - - - - Segment.Analytics.Http – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Http - - -

- - - - -
-

- - - - Summary -

- -
-

- Types -

-
-
- body() -
- -
-
-
- headers() -
- -
- -
- - - -
-

- Functions -

-
- - -

Issues a DELETE request to the given url

-
- -
-
- - -

Issues a DELETE request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues a GET request to the given url

-
- -
-
- - -

Issues a GET request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues a HEAD request to the given url

-
- -
-
- - -

Issues a HEAD request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues an OPTIONS request to the given url

-
- -
-
- - -

Issues a OPTIONS request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues a PATCH request to the given url

-
- -
-
- - -

Issues a PATCH request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues a POST request to the given url

-
- -
-
- - -

Issues a POST request to the given url, raising an exception in case of -failure

-
- -
- - -
- - -
-
- - -

Issues a PUT request to the given url

-
- -
-
- - -

Issues a PUT request to the given url, raising an exception in case of -failure

-
- -
-
- - -

Issues an HTTP request with the given method to the given url

-
- -
-
- - -

Issues an HTTP request with the given method to the given url, raising an -exception in case of failure

-
- -
-
-
- start() -
- -

Starts HTTPoison and its dependencies

-
- -
- -
- - - - - - -
- - - -
-

- - - - Types -

-
-
- -
- - - - body() - - - -
- -
body :: binary | {:form, [{atom, any}]} | {:file, binary}
- -
- -
-
- -
-
-
- -
- - - - headers() - - - -
- -
headers ::
-  [{binary, binary}] |
-  %{optional(binary) => binary}
- -
- -
-
- -
-
- -
-
- - - -
-

- - - - Functions -

-
- - - - - -
- - - - delete(url, headers \\ [], options \\ []) - - - -
- -
delete(binary, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a DELETE request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - delete!(url, headers \\ [], options \\ []) - - - - - -
-
-

Issues a DELETE request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - -
- - - - get(url, headers \\ [], options \\ []) - - - -
- -
get(binary, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a GET request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - get!(url, headers \\ [], options \\ []) - - - - - -
-
-

Issues a GET request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - -
- - - - head(url, headers \\ [], options \\ []) - - - -
- -
head(binary, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a HEAD request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - head!(url, headers \\ [], options \\ []) - - - - - -
-
-

Issues a HEAD request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - -
- - - - options(url, headers \\ [], options \\ []) - - - -
- -
options(binary, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues an OPTIONS request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - options!(url, headers \\ [], options \\ []) - - - -
- -
options!(binary, headers, Keyword.t) ::
-  HTTPoison.Response.t |
-  HTTPoison.AsyncResponse.t
- -
- -
-
-

Issues a OPTIONS request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - -
- - - - patch(url, body, headers \\ [], options \\ []) - - - -
- -
patch(binary, body, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a PATCH request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - patch!(url, body, headers \\ [], options \\ []) - - - - - -
-
-

Issues a PATCH request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - -
- - - - post(url, body, headers \\ [], options \\ []) - - - -
- -
post(binary, body, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a POST request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - post!(url, body, headers \\ [], options \\ []) - - - - - -
-
-

Issues a POST request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- -
- - - - process_options(options) - - - -
-
- -
-
-
- -
- - - - process_request_headers(headers) - - - -
-
- -
-
-
- -
- - - - process_url(url) - - - -
-
- -
-
-
- - - - - -
- - - - put(url, body, headers \\ [], options \\ []) - - - -
- -
put(binary, body, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues a PUT request to the given url.

-

Returns {:ok, response} if the request is successful, {:error, reason} -otherwise.

-

See request/5 for more detailed information.

- -
-
-
- - - - - -
- - - - put!(url, body, headers \\ [], options \\ []) - - - - - -
-
-

Issues a PUT request to the given url, raising an exception in case of -failure.

-

If the request does not fail, the response is returned.

-

See request!/5 for more detailed information.

- -
-
-
- - - - - - - -
- - - - request(method, url, body \\ "", headers \\ [], options \\ []) - - - -
- -
request(atom, binary, body, headers, Keyword.t) ::
-  {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} |
-  {:error, HTTPoison.Error.t}
- -
- -
-
-

Issues an HTTP request with the given method to the given url.

-

This function is usually used indirectly by get/3, post/4, put/4, etc

-

Args:

-
    -
  • method - HTTP method as an atom (:get, :head, :post, :put, -:delete, etc.) -
  • -
  • url - target url as a binary string or char list -
  • -
  • body - request body. See more below -
  • -
  • headers - HTTP headers as an orddict (e.g., [{"Accept", "application/json"}]) -
  • -
  • options - Keyword list of options -
  • -
-

Body:

-
    -
  • binary, char list or an iolist -
  • -
  • {:form, [{K, V}, ...]} - send a form url encoded -
  • -
  • {:file, "/path/to/file"} - send a file -
  • -
-

Options:

-
    -
  • :timeout - timeout to establish a connection, in milliseconds. Default is 8000 -
  • -
  • :recv_timeout - timeout used when receiving a connection. Default is 5000 -
  • -
  • :stream_to - a PID to stream the response to -
  • -
  • :proxy - a proxy to be used for the request; it can be a regular url -or a {Host, Proxy} tuple -
  • -
  • :proxy_auth - proxy authentication {User, Password} tuple -
  • -
  • :ssl - SSL options supported by the ssl erlang module -
  • -
  • :follow_redirect - a boolean that causes redirects to be followed -
  • -
  • :max_redirect - an integer denoting the maximum number of redirects to follow -
  • -
  • :params - an enumerable consisting of two-item tuples that will be appended to the url as query string parameters -
  • -
-

Timeouts can be an integer or :infinity

-

This function returns {:ok, response} or {:ok, async_response} if the -request is successful, {:error, reason} otherwise.

-

- - Examples -

- -
request(:post, "https://my.website.com", "{\"foo\": 3}", [{"Accept", "application/json"}])
- -
-
-
- - - - - - - -
- - - - request!(method, url, body \\ "", headers \\ [], options \\ []) - - - -
- -
request!(atom, binary, body, headers, Keyword.t) :: HTTPoison.Response.t
- -
- -
-
-

Issues an HTTP request with the given method to the given url, raising an -exception in case of failure.

-

request!/5 works exactly like request/5 but it returns just the -response in case of a successful request, raising an exception in case the -request fails.

- -
-
-
- -
- - - - start() - - - -
-
-

Starts HTTPoison and its dependencies.

- -
-
- -
- - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Identify.html b/doc/Segment.Analytics.Identify.html deleted file mode 100644 index aa080f8..0000000 --- a/doc/Segment.Analytics.Identify.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Identify – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Identify - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Page.html b/doc/Segment.Analytics.Page.html deleted file mode 100644 index ff70788..0000000 --- a/doc/Segment.Analytics.Page.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Page – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Page - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Screen.html b/doc/Segment.Analytics.Screen.html deleted file mode 100644 index 0d0561f..0000000 --- a/doc/Segment.Analytics.Screen.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Screen – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Screen - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.Track.html b/doc/Segment.Analytics.Track.html deleted file mode 100644 index abce2fb..0000000 --- a/doc/Segment.Analytics.Track.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Segment.Analytics.Track – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics.Track - - -

- - - - - - - - - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.Analytics.html b/doc/Segment.Analytics.html deleted file mode 100644 index f0e0b04..0000000 --- a/doc/Segment.Analytics.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - Segment.Analytics – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment.Analytics - - -

- - - - -
-

- - - - Summary -

- - - - - - - - - - -
- - - - - -
-

- - - - Functions -

-
- -
- - - - alias(alias) - - - -
-
- -
-
-
- - - -
- - - - alias(user_id, previous_id, context \\ Context.new()) - - - -
-
- -
-
-
- -
- - - - group(group) - - - -
-
- -
-
-
- - - - - -
- - - - group(user_id, group_id, traits \\ %{}, context \\ Context.new()) - - - -
-
- -
-
-
- -
- - - - identify(identify) - - - -
-
- -
-
-
- - - - - -
- - - - identify(user_id, traits \\ %{}, context \\ Context.new()) - - - -
-
- -
-
-
- -
- - - - page(page) - - - -
-
- -
-
-
- - - - - - - -
- - - - page(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) - - - -
-
- -
-
-
- -
- - - - screen(screen) - - - -
-
- -
-
-
- - - - - - - -
- - - - screen(user_id, name \\ "", properties \\ %{}, context \\ Context.new()) - - - -
-
- -
-
-
- -
- - - - track(track) - - - -
-
- -
-
-
- - - - - -
- - - - track(user_id, event, properties \\ %{}, context \\ Context.new()) - - - -
-
- -
-
- -
- - - - - - -
-
-
-
- - - - diff --git a/doc/Segment.html b/doc/Segment.html deleted file mode 100644 index e7c1808..0000000 --- a/doc/Segment.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - Segment – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- - -

- segment v0.1.1 - Segment - - -

- - - - -
-

- - - - Summary -

- -
-

- Types -

-
-
- status() -
- -
- -
- - - -
-

- Functions -

- -
- - -
- -
- - - - - - -
- - - -
-

- - - - Types -

-
-
- -
- - - - status() - - - -
- -
status :: :ok | :error
- -
- -
-
- -
-
- -
-
- - - -
-

- - - - Functions -

-
- -
- - - - start_link(write_key) - - - -
- -
start_link(binary) :: {Segment.status, pid}
- -
- -
-
- -
-
-
- -
- - - - write_key() - - - -
-
- -
-
- -
- - - - - - -
-
-
-
- - - - diff --git a/doc/api-reference.html b/doc/api-reference.html deleted file mode 100644 index a856c25..0000000 --- a/doc/api-reference.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - API Reference – segment v0.1.1 - - - - - - - - - -
- - - -
-
-
- -

- segment v0.1.1 - API Reference -

- - -
-

Modules

- -
- - - - - - -
-
-
-
- - - - diff --git a/doc/dist/app-091c05798a.css b/doc/dist/app-091c05798a.css deleted file mode 100644 index 18b03fa..0000000 --- a/doc/dist/app-091c05798a.css +++ /dev/null @@ -1 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Lato:300,700|Merriweather:300italic,300|Inconsolata:400,700);.hljs,article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}img,legend{border:0}.results ul,.sidebar ul{list-style:none}.night-mode-toggle:focus,.sidebar .sidebar-search .sidebar-searchInput:focus,.sidebar .sidebar-search .sidebar-searchInput:hover,.sidebar-toggle:active,.sidebar-toggle:focus,.sidebar-toggle:hover,a:active,a:hover{outline:0}.hljs-comment{color:#8e908c}.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.hljs-attribute,.hljs-regexp,.hljs-tag,.hljs-variable,.html .hljs-doctype,.ruby .hljs-constant,.xml .hljs-doctype,.xml .hljs-pi,.xml .hljs-tag .hljs-title{color:#c82829}.hljs-built_in,.hljs-constant,.hljs-literal,.hljs-number,.hljs-params,.hljs-pragma,.hljs-preprocessor{color:#f5871f}.css .hljs-rule .hljs-attribute,.ruby .hljs-class .hljs-title{color:#eab700}.hljs-header,.hljs-inheritance,.hljs-name,.hljs-string,.hljs-value,.ruby .hljs-symbol,.xml .hljs-cdata{color:#718c00}.css .hljs-hexcolor,.hljs-title{color:#3e999f}.coffeescript .hljs-title,.hljs-function,.javascript .hljs-title,.perl .hljs-sub,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-function .hljs-title,.ruby .hljs-title .hljs-keyword{color:#4271ae}.hljs-keyword,.javascript .hljs-function{color:#8959a8}.hljs{overflow-x:auto;background:#fff;color:#4d4d4c;padding:.5em;-webkit-text-size-adjust:none}legend,td,th{padding:0}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .css,.xml .hljs-cdata,.xml .javascript,.xml .vbscript{opacity:.5}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}.content-outer,body{background-color:#fff}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:icomoon;src:url(../fonts/icomoon.eot?h5z89e);src:url(../fonts/icomoon.eot?#iefixh5z89e) format('embedded-opentype'),url(../fonts/icomoon.ttf?h5z89e) format('truetype'),url(../fonts/icomoon.woff?h5z89e) format('woff'),url(../fonts/icomoon.svg?h5z89e#icomoon) format('svg');font-weight:400;font-style:normal}.icon-elem,[class*=" icon-"],[class^=icon-]{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.sidebar,body{font-family:Lato,sans-serif}.icon-link:before{content:"\e005"}.icon-search:before{content:"\e036"}.icon-cross:before{content:"\e117"}@media screen and (max-width:768px){.icon-menu{font-size:1em}}@media screen and (min-width:769px){.icon-menu{font-size:1.25em}}@media screen and (min-width:1281px){.icon-menu{font-size:1.5em}}.icon-menu:before{content:"\e120"}.icon-angle-right:before{content:"\f105"}.icon-code:before{content:"\f121"}body,html{box-sizing:border-box;height:100%;width:100%}body{margin:0;font-size:16px;line-height:1.6875em}*,:after,:before{box-sizing:inherit}.main{display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.sidebar{display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-webkit-box-direction:normal;-moz-box-direction:normal;min-height:0;-webkit-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;flex-direction:column;width:300px;height:100%;position:fixed;top:0;left:0;z-index:4}.content{width:100%;padding-left:300px;overflow-y:auto;-webkit-overflow-scrolling:touch;height:100%;position:relative;z-index:3}@media screen and (max-width:768px){body .content{z-index:0;padding-left:0}body .sidebar{z-index:3;-webkit-transform:translateX(-102%);transform:translateX(-102%);will-change:transform}}body.sidebar-closed .sidebar,body.sidebar-closing .sidebar,body.sidebar-opening .sidebar{z-index:0}body.sidebar-opened .sidebar-toggle,body.sidebar-opening .sidebar-toggle{-webkit-transform:translateX(250px);transform:translateX(250px)}@media screen and (max-width:768px){body.sidebar-opened .sidebar,body.sidebar-opening .sidebar{-webkit-transform:translateX(0);transform:translateX(0)}}body.sidebar-closed .content,body.sidebar-closing .content{padding-left:0}body.sidebar-closed .sidebar-toggle,body.sidebar-closing .sidebar-toggle{-webkit-transform:none;transform:none}body.sidebar-closed .icon-menu{color:#000}.sidebar-toggle i,.sidebar-toggle:hover{color:#e1e1e1}body.sidebar-opening .sidebar-toggle{transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out}body.sidebar-opening .content{padding-left:300px;transition:padding-left .3s ease-in-out}@media screen and (max-width:768px){body.sidebar-opening .content{padding-left:0}body.sidebar-opening .sidebar{transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;z-index:3}}body.sidebar-closing .sidebar-toggle{transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out}body.sidebar-closing .content{transition:padding-left .3s ease-in-out}@media screen and (max-width:768px){body.sidebar-closing .sidebar{z-index:3;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;-webkit-transform:translateX(-102%);transform:translateX(-102%)}}.sidebar a,.sidebar-toggle{transition:color .3s ease-in-out}body.sidebar-closed .sidebar{visibility:hidden}.content-inner{max-width:949px;margin:0 auto;padding:3px 60px}.content-outer{min-height:100%}@media screen and (max-width:768px){.content-inner{padding:27px 20px 27px 40px}}.sidebar-toggle{position:fixed;z-index:99;left:18px;top:8px;background-color:transparent;border:none;padding:0;font-size:16px;will-change:transform;-webkit-transform:translateX(250px);transform:translateX(250px)}@media screen and (max-width:768px){.sidebar-toggle{-webkit-transform:translateX(0);transform:translateX(0);left:5px;top:5px}.sidebar-opened .sidebar-toggle{left:18px;top:5px}}.sidebar{font-size:15px;line-height:18px;background:#373f52;color:#d5dae6;overflow:hidden}.sidebar .gradient{background:linear-gradient(#373f52,rgba(55,63,82,0));height:20px;margin-top:-20px;pointer-events:none;position:relative;top:20px;z-index:100}.sidebar ul li{margin:0;padding:0 10px}.sidebar a{color:#d5dae6;text-decoration:none}.sidebar a:hover{color:#fff}.sidebar .sidebar-projectLink{margin:23px 30px 0}.sidebar .sidebar-projectDetails{display:inline-block;text-align:right;vertical-align:top;margin-top:6px}.sidebar .sidebar-projectImage{display:inline-block;max-width:64px;max-height:64px;margin-left:15px;vertical-align:bottom}.sidebar .sidebar-projectName{font-weight:700;font-size:24px;line-height:30px;color:#fff;margin:0;padding:0;max-width:155px}.sidebar .sidebar-projectVersion{margin:0;padding:0;font-weight:300;font-size:16px;line-height:20px;color:#fff}.sidebar .sidebar-listNav{padding:10px 30px 20px;margin:0}.sidebar .sidebar-listNav li,.sidebar .sidebar-listNav li a{text-transform:uppercase;font-weight:300;font-size:14px}.sidebar .sidebar-listNav li{padding-left:17px;border-left:3px solid transparent;transition:all .3s linear;line-height:27px}.sidebar .sidebar-listNav li.selected,.sidebar .sidebar-listNav li.selected a,.sidebar .sidebar-listNav li:hover,.sidebar .sidebar-listNav li:hover a{border-color:#9768d1;color:#fff}.sidebar .sidebar-search{margin:23px 30px 18px;display:-webkit-flex;display:-ms-flexbox;display:-ms-flex;display:flex}.sidebar .sidebar-search i.icon-search{font-size:14px;color:#d5dae6}.sidebar .sidebar-search .sidebar-searchInput{background-color:transparent;border:none;border-radius:0;border-bottom:1px solid #959595;margin-left:5px;height:20px}.sidebar #full-list{margin:0 0 0 30px;padding:10px 20px 40px;overflow-y:auto;-webkit-overflow-scrolling:touch;-webkit-flex:1 1 .01%;-moz-flex:1 1 .01%;-ms-flex:1 1 .01%;flex:1 1 .01%;-ms-flex-positive:1;-ms-flex-negative:1;-ms-flex-preferred-size:.01%}.sidebar #full-list ul{display:none;margin:9px 15px;padding:0}.sidebar #full-list ul li{font-weight:300;line-height:18px;padding:2px 10px}.sidebar #full-list ul li a.expand:before{content:"+";font-family:monospaced;font-size:15px;float:left;width:13px;margin-left:-13px}.sidebar #full-list ul li.open a.expand:before{content:"−"}.sidebar #full-list ul li ul{display:none;margin:9px 6px}.sidebar #full-list li.open>ul,.sidebar #full-list ul li.open>ul{display:block}.sidebar #full-list ul li ul li{border-left:1px solid #959595;padding:0 10px}.sidebar #full-list ul li ul li.active:before{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f105";margin-left:-10px;font-size:16px;margin-right:5px}.sidebar #full-list li{padding:0;line-height:27px}.sidebar #full-list li.active{border-left:none}.sidebar #full-list li.active>a,.sidebar #full-list li.clicked>a{color:#fff}.sidebar #full-list li.group{text-transform:uppercase;font-weight:700;font-size:.8em;margin:2em 0 0;line-height:1.8em;color:#ddd}@media screen and (max-height:500px){.sidebar{overflow-y:auto}.sidebar #full-list{overflow:visible}}.content-inner{font-family:Merriweather,serif;font-size:1em;line-height:1.6875em}.content-inner h1,.content-inner h2,.content-inner h3,.content-inner h4,.content-inner h5,.content-inner h6{font-family:Lato,sans-serif;font-weight:700;line-height:1.5em;word-wrap:break-word}.content-inner h1{font-size:2em;margin:1em 0 .5em}.content-inner h1.section-heading{margin:1.5em 0 .5em}.content-inner h1 small{font-weight:300}.content-inner h1 a.view-source{font-size:1.2rem}.content-inner h2{font-size:1.6em;margin:1em 0 .5em;font-weight:700}.content-inner h3{font-size:1.375em;margin:1em 0 .5em;font-weight:700}.content-inner a{color:#000;text-decoration:none;text-shadow:.03em 0 #fff,-.03em 0 #fff,0 .03em #fff,0 -.03em #fff,.06em 0 #fff,-.06em 0 #fff,.09em 0 #fff,-.09em 0 #fff,.12em 0 #fff,-.12em 0 #fff,.15em 0 #fff,-.15em 0 #fff;background-image:linear-gradient(#fff,#fff),linear-gradient(#fff,#fff),linear-gradient(#000,#000);background-size:.05em 1px,.05em 1px,1px 1px;background-repeat:no-repeat,no-repeat,repeat-x;background-position:0 90%,100% 90%,0 90%}.content-inner a:selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner a:-moz-selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner a *,.content-inner a :after,.content-inner a :before,.content-inner a:after,.content-inner a:before{text-shadow:none}.content-inner a:visited{color:#000}.content-inner ul li{line-height:1.5em}.content-inner ul li>p{margin:0}.content-inner a.view-source{float:right;color:#959595;background:0 0;border:none;text-shadow:none;transition:color .3s ease-in-out;margin-top:1px}.content-inner a.view-source:hover{color:#373f52}.content-inner .note{color:#959595;margin:0 5px;font-size:14px;font-weight:400}.content-inner blockquote{font-style:italic;margin:.5em 0;padding:.25em 1.5em;border-left:3px solid #e1e1e1;display:inline-block}.content-inner blockquote :first-child{padding-top:0;margin-top:0}.content-inner blockquote :last-child{padding-bottom:0;margin-bottom:0}.content-inner table{margin:2em 0}.content-inner th{text-align:left;font-family:Lato,sans-serif;text-transform:uppercase;font-weight:700;padding-bottom:.5em}.content-inner tr{border-bottom:1px solid #d5dae6;vertical-align:bottom;height:2.5em}.content-inner .summary .summary-row .summary-signature a,.content-inner .summary h2 a{background:0 0;border:none;text-shadow:none}.content-inner td,.content-inner th{padding-left:1em;line-height:2em}.content-inner .section-heading:hover a.hover-link{opacity:1;text-decoration:none}.content-inner .section-heading a.hover-link{transition:opacity .3s ease-in-out;display:inline-block;opacity:0;padding:.3em .6em .6em;line-height:1em;margin-left:-2.7em;background:0 0;border:none;text-shadow:none;font-size:16px;vertical-align:middle}.content-inner .detail h2.section-heading{margin:1.5em 0 .5em .3em}.content-inner .visible-xs{display:none!important}@media screen and (max-width:767px){.content-inner .visible-xs{display:block!important}}.content-inner img{max-width:100%}.content-inner .summary h2{font-weight:700}.content-inner .summary .summary-row .summary-signature{font-family:Inconsolata,Menlo,Courier,monospace;font-weight:700}.content-inner .summary .summary-row .summary-synopsis{font-family:Merriweather,serif;font-style:italic;padding:0 1.2em;margin:0 0 .5em}.content-inner .summary .summary-row .summary-synopsis p{margin:0;padding:0}.content-inner .detail-header{margin:1.5em 0 1em;padding:.5em 1em;background:#f7f7f7;border-left:3px solid #9768d1;font-size:1em;font-family:Inconsolata,Menlo,Courier,monospace;position:relative}.content-inner .detail-header .note{float:right}.content-inner .detail-header .signature{font-size:1rem;font-weight:700}.content-inner .detail-header:hover a.detail-link{opacity:1;text-decoration:none}.content-inner .detail-header a.detail-link{transition:opacity .3s ease-in-out;position:absolute;top:0;left:0;display:block;opacity:0;padding:.6em;line-height:1.5em;margin-left:-2.5em;background:0 0;border:none;text-shadow:none}.content-inner .footer .line,.search-results h1{display:inline-block}.content-inner .specs pre,.content-inner code{font-family:Inconsolata,Menlo,Courier,monospace;font-style:normal;line-height:24px}.content-inner .specs{opacity:.7;padding-bottom:.05em}.content-inner .specs pre{font-size:.9em;margin:0;padding:0}.content-inner .docstring{margin:1.2em 0 2.1em 1.2em}.content-inner .docstring h2,.content-inner .docstring h3,.content-inner .docstring h4,.content-inner .docstring h5{font-weight:700}.content-inner .docstring h2{font-size:1em}.content-inner .docstring h3{font-size:.95em}.content-inner .docstring h4{font-size:.9em}.content-inner .docstring h5{font-size:.85em}.content-inner a.no-underline,.content-inner pre a{color:#9768d1;text-shadow:none;text-decoration:none;background-image:none}.content-inner a.no-underline:active,.content-inner a.no-underline:focus,.content-inner a.no-underline:hover,.content-inner a.no-underline:visited,.content-inner pre a:active,.content-inner pre a:focus,.content-inner pre a:hover,.content-inner pre a:visited{color:#9768d1;text-decoration:none}.content-inner code{font-weight:400;background-color:#f7f9fc;border:1px solid #e1e1e1;vertical-align:baseline;border-radius:2px;padding:0 .5em}.content-inner pre{margin:1.5em 0}.content-inner pre.spec{margin:0}.content-inner pre.spec code{padding:0}.content-inner pre code.hljs{white-space:inherit;padding:.5em 1em;background-color:#f7f9fc}.content-inner .footer{margin:4em auto 1em;text-align:center;font-style:italic;font-size:14px;color:#959595}.content-inner .footer a{color:#959595;text-decoration:none;text-shadow:.03em 0 #fff,-.03em 0 #fff,0 .03em #fff,0 -.03em #fff,.06em 0 #fff,-.06em 0 #fff,.09em 0 #fff,-.09em 0 #fff,.12em 0 #fff,-.12em 0 #fff,.15em 0 #fff,-.15em 0 #fff;background-image:linear-gradient(#fff,#fff),linear-gradient(#fff,#fff),linear-gradient(#959595,#959595);background-size:.05em 1px,.05em 1px,1px 1px;background-repeat:no-repeat,no-repeat,repeat-x;background-position:0 90%,100% 90%,0 90%}.content-inner .footer a:selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.content-inner .footer a:-moz-selection{text-shadow:.03em 0 #b4d5fe,-.03em 0 #b4d5fe,0 .03em #b4d5fe,0 -.03em #b4d5fe,.06em 0 #b4d5fe,-.06em 0 #b4d5fe,.09em 0 #b4d5fe,-.09em 0 #b4d5fe,.12em 0 #b4d5fe,-.12em 0 #b4d5fe,.15em 0 #b4d5fe,-.15em 0 #b4d5fe;background:#b4d5fe}.results .result-id a,.search-results a.close-search{background-image:none;transition:color .3s ease-in-out;text-shadow:none}.content-inner .footer a *,.content-inner .footer a :after,.content-inner .footer a :before,.content-inner .footer a:after,.content-inner .footer a:before{text-shadow:none}.content-inner .footer a:visited{color:#959595}.search-results a.close-search{display:inline-block;float:right}.search-results a.close-search:active,.search-results a.close-search:focus,.search-results a.close-search:visited{color:#000}.search-results a.close-search:hover{color:#9768d1}.results .result-id{font-size:1.2em}.results .result-id a:active,.results .result-id a:focus,.results .result-id a:visited{color:#000}.results .result-id a:hover{color:#9768d1}.results .result-elem em,.results .result-id em{font-style:normal;color:#9768d1}.results ul{margin:0;padding:0}.night-mode-toggle{background:0 0;border:none}.night-mode-toggle:after{font-size:12px;content:'Switch to night mode';text-decoration:underline}body.night-mode{background:#212127}body.night-mode .hljs-comment{color:#969896}body.night-mode .css .hljs-class,body.night-mode .css .hljs-id,body.night-mode .css .hljs-pseudo,body.night-mode .hljs-attribute,body.night-mode .hljs-regexp,body.night-mode .hljs-tag,body.night-mode .hljs-variable,body.night-mode .html .hljs-doctype,body.night-mode .ruby .hljs-constant,body.night-mode .xml .hljs-doctype,body.night-mode .xml .hljs-pi,body.night-mode .xml .hljs-tag .hljs-title{color:#c66}body.night-mode .hljs-built_in,body.night-mode .hljs-constant,body.night-mode .hljs-literal,body.night-mode .hljs-number,body.night-mode .hljs-params,body.night-mode .hljs-pragma,body.night-mode .hljs-preprocessor{color:#de935f}body.night-mode .css .hljs-rule .hljs-attribute,body.night-mode .ruby .hljs-class .hljs-title{color:#f0c674}body.night-mode .hljs-header,body.night-mode .hljs-inheritance,body.night-mode .hljs-name,body.night-mode .hljs-string,body.night-mode .hljs-value,body.night-mode .ruby .hljs-symbol,body.night-mode .xml .hljs-cdata{color:#b5bd68}body.night-mode .css .hljs-hexcolor,body.night-mode .hljs-title{color:#8abeb7}body.night-mode .coffeescript .hljs-title,body.night-mode .hljs-function,body.night-mode .javascript .hljs-title,body.night-mode .perl .hljs-sub,body.night-mode .python .hljs-decorator,body.night-mode .python .hljs-title,body.night-mode .ruby .hljs-function .hljs-title,body.night-mode .ruby .hljs-title .hljs-keyword{color:#81a2be}body.night-mode .hljs-keyword,body.night-mode .javascript .hljs-function{color:#b294bb}body.night-mode .hljs{display:block;overflow-x:auto;background:#1d1f21;color:#c5c8c6;padding:.5em;-webkit-text-size-adjust:none}body.night-mode .coffeescript .javascript,body.night-mode .javascript .xml,body.night-mode .tex .hljs-formula,body.night-mode .xml .css,body.night-mode .xml .hljs-cdata,body.night-mode .xml .javascript,body.night-mode .xml .vbscript{opacity:.5}body.night-mode .content-outer{background:#212127}body.night-mode .night-mode-toggle:after{color:#959595;content:'Switch to day mode';text-decoration:underline}body.night-mode .close-search:active,body.night-mode .close-search:focus,body.night-mode .close-search:visited,body.night-mode .results .result-id a:active,body.night-mode .results .result-id a:focus,body.night-mode .results .result-id a:visited{color:#d2d2d2}body.night-mode .close-search:hover,body.night-mode .results .result-id a:hover{color:#9768d1}body.night-mode .content-inner{color:#b4b4b4}body.night-mode .content-inner h1,body.night-mode .content-inner h2,body.night-mode .content-inner h3,body.night-mode .content-inner h4,body.night-mode .content-inner h5,body.night-mode .content-inner h6{color:#d2d2d2}body.night-mode .content-inner a{color:#d2d2d2;text-decoration:none;text-shadow:none;background-image:none}body.night-mode .content-inner .detail-header{background:#3a4152;color:#d2d2d2}body.night-mode .content-inner code,body.night-mode .content-inner pre code.hljs{background-color:#2c2c31;border:1px solid #38383d}body.night-mode .content-inner .footer{color:#959595}body.night-mode .content-inner .footer .line{display:inline-block}body.night-mode .content-inner .footer a{color:#959595;text-shadow:none;background-image:none;text-decoration:underline}.night-mode .sidebar-toggle i{color:#d5dae6}@media print{#sidebar{display:none}} \ No newline at end of file diff --git a/doc/dist/app-574613960f.js b/doc/dist/app-574613960f.js deleted file mode 100644 index d4322a4..0000000 --- a/doc/dist/app-574613960f.js +++ /dev/null @@ -1,6 +0,0 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,i){r.apply(this,[e,t,i].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){"use strict";var r=n(1)["default"],i=n(2),a=r(i),o=n(3),s=r(o),l=n(4),u=n(95),c=n(96);window.$=a["default"],a["default"](function(){s["default"].configure({tabReplace:" ",languages:[]}),c.initialize(),u.initialize(),l.initialize(),s["default"].initHighlighting()})},function(e,t){"use strict";t["default"]=function(e){return e&&e.__esModule?e:{"default":e}},t.__esModule=!0},function(e,t,n){var r,i;!function(t,n){"object"==typeof e&&"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(t)}("undefined"!=typeof window?window:this,function(n,a){function o(e){var t="length"in e&&e.length,n=re.type(e);return"function"===n||re.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function s(e,t,n){if(re.isFunction(t))return re.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return re.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(fe.test(t))return re.filter(t,e,n);t=re.filter(t,e)}return re.grep(e,function(e){return V.call(t,e)>=0!==n})}function l(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function u(e){var t=ye[e]={};return re.each(e.match(ve)||[],function(e,n){t[n]=!0}),t}function c(){te.removeEventListener("DOMContentLoaded",c,!1),n.removeEventListener("load",c,!1),re.ready()}function f(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=re.expando+f.uid++}function d(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Ne,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Ee.test(n)?re.parseJSON(n):n}catch(i){}we.set(e,t,n)}else n=void 0;return n}function p(){return!0}function h(){return!1}function g(){try{return te.activeElement}catch(e){}}function m(e,t){return re.nodeName(e,"table")&&re.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function v(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function y(e){var t=Be.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function b(e,t){for(var n=0,r=e.length;r>n;n++)_e.set(e[n],"globalEval",!t||_e.get(t[n],"globalEval"))}function x(e,t){var n,r,i,a,o,s,l,u;if(1===t.nodeType){if(_e.hasData(e)&&(a=_e.access(e),o=_e.set(t,a),u=a.events)){delete o.handle,o.events={};for(i in u)for(n=0,r=u[i].length;r>n;n++)re.event.add(t,i,u[i][n])}we.hasData(e)&&(s=we.access(e),l=re.extend({},s),we.set(t,l))}}function _(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&re.nodeName(e,t)?re.merge([e],n):n}function w(e,t){var n=t.nodeName.toLowerCase();"input"===n&&Se.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}function E(e,t){var r,i=re(t.createElement(e)).appendTo(t.body),a=n.getDefaultComputedStyle&&(r=n.getDefaultComputedStyle(i[0]))?r.display:re.css(i[0],"display");return i.detach(),a}function N(e){var t=te,n=We[e];return n||(n=E(e,t),"none"!==n&&n||(ze=(ze||re("