Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions lib/impact_tracker/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,56 @@ defmodule ImpactTracker.Project do
field :cleartext_uuid, Ecto.UUID
field :hashed_uuid, :string
field :no_of_active_users, :integer
field :no_of_monthly_active_users, :integer
field :no_of_users, :integer

timestamps()
end

def v2_changeset(project, params) do
cast_attrs = [
:cleartext_uuid,
:hashed_uuid,
:no_of_active_users,
:no_of_users
]
changeset(project, params, include_monthly_active_users: false)
end

# Version 3 adds a 30-day `no_of_monthly_active_users` alongside the existing
# 90-day `no_of_active_users`.
def v3_changeset(project, params) do
changeset(project, params, include_monthly_active_users: true)
end

defp changeset(project, params, include_monthly_active_users: include_mau) do
mau_attrs = if include_mau, do: [:no_of_monthly_active_users], else: []

required_attrs = [:hashed_uuid, :no_of_active_users, :no_of_users]
cast_attrs =
[
:cleartext_uuid,
:hashed_uuid,
:no_of_active_users,
:no_of_users
] ++ mau_attrs

required_attrs =
[:hashed_uuid, :no_of_active_users, :no_of_users] ++ mau_attrs

project
|> cast(params, cast_attrs)
|> validate_required(required_attrs)
|> validate_number(:no_of_active_users, greater_than_or_equal_to: 0)
|> validate_number(:no_of_users, greater_than_or_equal_to: 0)
|> maybe_validate_monthly_active_users(include_mau)
|> validate_hashed_uuid()
# Note - at the moment, there does not appear to be a cost-effective way
# to validate that the `workflows` element is present
|> cast_assoc(:workflows, with: &Workflow.v2_changeset/2)
end

defp maybe_validate_monthly_active_users(changeset, true) do
validate_number(changeset, :no_of_monthly_active_users,
greater_than_or_equal_to: 0
)
end

defp maybe_validate_monthly_active_users(changeset, false), do: changeset

defp validate_hashed_uuid(changeset = %{changes: %{cleartext_uuid: cleartext}}) do
validate_change(changeset, :hashed_uuid, fn _, hash ->
if hash == Base.encode16(:crypto.hash(:sha256, cleartext)) do
Expand Down
75 changes: 53 additions & 22 deletions lib/impact_tracker/submission.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule ImpactTracker.Submission do
alias ImpactTracker.Project

# When adding new versions, update this.
@supported_versions ["1", "2"]
@supported_versions ["1", "2", "3"]

@primary_key {:id, :binary_id, autogenerate: true}
schema "submissions" do
Expand All @@ -21,6 +21,7 @@ defmodule ImpactTracker.Submission do
field :instance_id, Ecto.UUID
field :lightning_version, :string
field :no_of_active_users, :integer
field :no_of_monthly_active_users, :integer
field :no_of_users, :integer
field :operating_system, :string
field :report_date, :date
Expand Down Expand Up @@ -48,49 +49,79 @@ defmodule ImpactTracker.Submission do
end

defp versioned_setup(changeset = %{changes: %{version: "2"}}, all_attrs) do
build_submission(changeset, all_attrs, include_monthly_active_users: false)
end

# Version 3 adds a 30-day `no_of_monthly_active_users` (true MAU) alongside
# the existing 90-day `no_of_active_users`, at both instance and project level.
defp versioned_setup(changeset = %{changes: %{version: "3"}}, all_attrs) do
build_submission(changeset, all_attrs, include_monthly_active_users: true)
end

defp build_submission(changeset, all_attrs,
include_monthly_active_users: include_mau
) do
submission_attrs =
all_attrs |> extract_submission_attrs()

cast_attrs = [
:country,
:generated_at,
:lightning_version,
:no_of_active_users,
:no_of_users,
:operating_system,
:region,
:report_date,
:version
]

required_attrs = [
:generated_at,
:lightning_version,
:no_of_active_users,
:no_of_users,
:operating_system,
:report_date
]
mau_attrs = if include_mau, do: [:no_of_monthly_active_users], else: []

cast_attrs =
[
:country,
:generated_at,
:lightning_version,
:no_of_active_users,
:no_of_users,
:operating_system,
:region,
:report_date,
:version
] ++ mau_attrs

required_attrs =
[
:generated_at,
:lightning_version,
:no_of_active_users,
:no_of_users,
:operating_system,
:report_date
] ++ mau_attrs

project_changeset =
if include_mau, do: &Project.v3_changeset/2, else: &Project.v2_changeset/2

changeset
|> cast(submission_attrs, cast_attrs)
|> validate_required(required_attrs)
|> validate_number(:no_of_active_users, greater_than_or_equal_to: 0)
|> validate_number(:no_of_users, greater_than_or_equal_to: 0)
|> maybe_validate_monthly_active_users(include_mau)
|> unique_constraint(
[:instance_id, :report_date],
message: "instance already has a submission for this date"
)
|> cast_assoc(:projects, with: &Project.v2_changeset/2)
|> cast_assoc(:projects, with: project_changeset)
end

defp maybe_validate_monthly_active_users(changeset, true) do
validate_number(changeset, :no_of_monthly_active_users,
greater_than_or_equal_to: 0
)
end

defp maybe_validate_monthly_active_users(changeset, false), do: changeset

defp extract_submission_attrs(attrs) do
%{
country: attrs |> extract_attr("country"),
generated_at: attrs |> extract_attr("generated_at"),
lightning_version: attrs |> extract_attr("instance", "version"),
no_of_active_users:
attrs |> extract_attr("instance", "no_of_active_users"),
no_of_monthly_active_users:
attrs |> extract_attr("instance", "no_of_monthly_active_users"),
no_of_users: attrs |> extract_attr("instance", "no_of_users"),
operating_system: attrs |> extract_attr("instance", "operating_system"),
projects: attrs |> extract_attr("projects"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule ImpactTracker.Repo.Migrations.AddNoOfMonthlyActiveUsers do
use Ecto.Migration

def change do
alter table(:submissions) do
add :no_of_monthly_active_users, :integer, null: true
end

alter table(:projects) do
add :no_of_monthly_active_users, :integer, null: true
end
end
end
79 changes: 79 additions & 0 deletions test/impact_tracker/project_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,81 @@ defmodule ImpactTracker.ProjectTest do
end
end

describe "v3_changeset/2" do
setup do
%{data: build_v3_project_data()}
end

test "returns a valid changeset including no_of_monthly_active_users", %{
data: data
} do
changeset = %Project{} |> Project.v3_changeset(data)

assert %Changeset{valid?: true, changes: changes} = changeset

assert(
%{
no_of_active_users: 7,
no_of_monthly_active_users: 4,
no_of_users: 10
} = changes
)

assert changes.workflows |> Enum.count() == 2
end

test "validates the presence of no_of_monthly_active_users", %{data: data} do
changeset =
%Project{}
|> Project.v3_changeset(
data
|> remove_data("no_of_monthly_active_users")
)

assert %Changeset{valid?: false, errors: errors} = changeset

assert(
[
no_of_monthly_active_users:
{"can't be blank", [{:validation, :required}]}
] = errors
)
end

test "validates that no_of_monthly_active_users >= 0", %{data: data} do
changeset =
%Project{}
|> Project.v3_changeset(
data
|> modify_data("no_of_monthly_active_users", -1)
)

assert %Changeset{valid?: false, errors: errors} = changeset

assert(
[
no_of_monthly_active_users: {
"must be greater than or equal to %{number}",
[
{:validation, :number},
{:kind, :greater_than_or_equal_to},
{:number, 0}
]
}
] = errors
)

changeset =
%Project{}
|> Project.v3_changeset(
data
|> modify_data("no_of_monthly_active_users", 0)
)

assert %Changeset{valid?: true} = changeset
end
end

defp build_project_data do
uuid = generate_uuid()

Expand All @@ -216,6 +291,10 @@ defmodule ImpactTracker.ProjectTest do
}
end

defp build_v3_project_data do
build_project_data() |> Map.put("no_of_monthly_active_users", 4)
end

defp build_workflow_data do
uuid_1 = generate_uuid()
uuid_2 = generate_uuid()
Expand Down
Loading
Loading