File size: 1,839 Bytes
936b397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
defmodule Plausible.Funnel do
  @min_steps 2
  @max_steps 8

  @moduledoc """
  A funnel is a marketing term used to capture and describe the journey
  that users go through, from initial step to conversion.
  A funnel consists of several steps (here: #{@min_steps}..#{@max_steps}).

  This module defines the database schema for storing funnels
  and changeset helpers for enumerating the steps within.

  Each step references a goal (either a Custom Event or Visit)
  - see: `Plausible.Goal`.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias Plausible.Funnel.Step

  defmacro min_steps() do
    quote do
      unquote(@min_steps)
    end
  end

  defmacro max_steps() do
    quote do
      unquote(@max_steps)
    end
  end

  defmacro __using__(_opts \\ []) do
    quote do
      require Plausible.Funnel
      alias Plausible.Funnel
    end
  end

  @type t() :: %__MODULE__{}
  schema "funnels" do
    field :name, :string
    field :strict_order, :boolean, default: false
    belongs_to :site, Plausible.Site

    has_many :steps, Step,
      preload_order: [
        asc: :step_order
      ],
      on_replace: :delete

    has_many :goals, through: [:steps, :goal]
    timestamps()
  end

  def changeset(funnel \\ %__MODULE__{}, attrs \\ %{}) do
    funnel
    |> cast(attrs, [:name, :strict_order])
    |> validate_required([:name])
    |> put_steps(attrs[:steps] || attrs["steps"])
    |> validate_length(:steps, min: @min_steps, max: @max_steps)
    |> unique_constraint(:name,
      name: :funnels_name_site_id_index
    )
  end

  def put_steps(changeset, steps) do
    steps
    |> Enum.map(&Step.changeset(%Step{}, &1))
    |> Enum.with_index(fn step, step_order ->
      Ecto.Changeset.put_change(step, :step_order, step_order + 1)
    end)
    |> then(&Ecto.Changeset.put_assoc(changeset, :steps, &1))
  end
end