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
13 changes: 13 additions & 0 deletions lib/code_corps/helpers/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,22 @@ defmodule CodeCorps.Helpers.Query do
end
def project_id_with_number_filter(query, _), do: query

def task_list_id_with_number_filter(query, %{"id" => number, "task_list_id" => task_list_id}) do
query |> where([object], object.number == ^number and object.task_list_id == ^task_list_id)
end
def task_list_id_with_number_filter(query, _), do: query

def project_filter(query, %{"project_id" => project_id}) do
query |> where([object], object.project_id == ^project_id)
end
def project_filter(query, _), do: query

def task_list_filter(query, %{"task_list_ids" => task_list_ids}) do
task_list_ids = task_list_ids |> coalesce_id_string
query |> where([object], object.task_list_id in ^task_list_ids)
end
def task_list_filter(query, _), do: query

def task_type_filter(query, %{"task_type" => task_type_list}) do
task_types = task_type_list |> coalesce_string
query |> where([object], object.task_type in ^task_types)
Expand All @@ -58,6 +69,8 @@ defmodule CodeCorps.Helpers.Query do

def sort_by_newest_first(query), do: query |> order_by([desc: :inserted_at])

def sort_by_order(query), do: query |> order_by([asc: :order])

# end sorting

# finders
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ defmodule CodeCorps.Mixfile do
{:stripity_stripe, "~> 2.0.0-alpha.5"}, # Stripe
{:timber, "~> 0.4"}, # Logging
{:timex, "~> 3.0"},
{:timex_ecto, "~> 3.0"}
{:timex_ecto, "~> 3.0"},
{:ecto_ordered, "0.2.0-beta1"}
]
end

Expand Down
9 changes: 5 additions & 4 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
"canary": {:hex, :canary, "1.1.0", "3599012f5393c2fdb18c9129853a9fc6cd115ebfdcc0725af7b52398b9ed5c7b", [:mix], [{:canada, "~> 1.0.0", [hex: :canada, optional: false]}, {:ecto, ">= 1.1.0", [hex: :ecto, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]},
"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []},
"combine": {:hex, :combine, "0.9.3", "192e609b48b3f2210494e26f85db1712657be1a8f15795656710317ea43fc449", [:mix], []},
"comeonin": {:hex, :comeonin, "2.6.0", "74c288338b33205f9ce97e2117bb9a2aaab103a1811d243443d76fdb62f904ac", [:make, :make, :mix], []},
"comeonin": {:hex, :comeonin, "2.6.0", "74c288338b33205f9ce97e2117bb9a2aaab103a1811d243443d76fdb62f904ac", [:mix, :make, :make], []},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
"corsica": {:hex, :corsica, "0.5.0", "eb5b2fccc5bc4f31b8e2b77dd15f5f302aca5d63286c953e8e916f806056d50c", [:mix], [{:cowboy, ">= 1.0.0", [hex: :cowboy, optional: false]}, {:plug, ">= 0.9.0", [hex: :plug, optional: false]}]},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"credo": {:hex, :credo, "0.5.2", "92e8c9f86e0ffbf9f688595e9f4e936bc96a52e5606d2c19713e9e4d191d5c74", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]},
"db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
"decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"ecto": {:hex, :ecto, "2.0.6", "9dcbf819c2a77f67a66b83739b7fcc00b71aaf6c100016db4f798930fa4cfd47", [:mix], [{:db_connection, "~> 1.0", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]},
"ecto_ordered": {:hex, :ecto_ordered, "0.2.0-beta1", "cb066bc608f1c8913cea85af8293261720e6a88e3c99061e6877d7025352f045", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}]},
"ex_aws": {:hex, :ex_aws, "0.5.0", "6ca02f1e8fe8340aa2eee66d9f08efcd6ff1f9f4ef7264669d0756e0b3917218", [:mix], [{:httpoison, "~> 0.8", [hex: :httpoison, optional: true]}, {:jsx, "~> 2.5", [hex: :jsx, optional: true]}, {:poison, "~> 1.2 or ~> 2.0", [hex: :poison, optional: true]}, {:sweet_xml, "~> 0.5", [hex: :sweet_xml, optional: true]}]},
"ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"ex_machina": {:hex, :ex_machina, "1.0.2", "1cc49e1a09e3f7ab2ecb630c17f14c2872dc4ec145d6d05a9c3621936a63e34f", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: true]}]},
Expand All @@ -26,7 +27,7 @@
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
"gettext": {:hex, :gettext, "0.12.1", "c0624f52763469ef7a3674919ae28b8286d88195b90fa1516180f31bbbd26d14", [:mix], []},
"guardian": {:hex, :guardian, "0.13.0", "37c5b5302617276093570ee938baca146f53e1d5de1f5c2b8effb1d2fea596d2", [:mix], [{:jose, "~> 1.8", [hex: :jose, optional: false]}, {:phoenix, "~> 1.2.0", [hex: :phoenix, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:poison, ">= 1.3.0", [hex: :poison, optional: false]}, {:uuid, ">=1.1.1", [hex: :uuid, optional: false]}]},
"hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:mix, :rebar3], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [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]}]},
"hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:rebar3, :mix], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [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.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]},
"idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
"inch_ex": {:hex, :inch_ex, "0.5.5", "b63f57e281467bd3456461525fdbc9e158c8edbe603da6e3e4671befde796a3d", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
Expand All @@ -53,7 +54,7 @@
"scrivener_ecto": {:hex, :scrivener_ecto, "1.0.2", "4b10a2e6c23ed8aae59731d7ae71bfd55afea6559aae61b124e6e521055b4a9c", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:postgrex, "~> 0.11.0 or ~> 0.12.0", [hex: :postgrex, optional: true]}, {:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}]},
"segment": {:git, "https://github.com/stueccles/analytics-elixir.git", "8fe520c16a8a9290d55c849bf4d67420396e1cdd", []},
"sentry": {:hex, :sentry, "2.0.2", "f08638758f7bf891e238466009f6cd702fc26d87286663af26927a78ed149346", [:mix], [{:hackney, "~> 1.6.1", [hex: :hackney, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, optional: false]}]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:rebar, :make], []},
"stripe_eventex": {:hex, :stripe_eventex, "1.0.0", "782016598b751c0fdb5489038c92c30a5aab034636d0d9d3a486f75a01fbf0b6", [:mix], [{:cowboy, "~> 1.0.0", [hex: :cowboy, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:poison, "~> 2.0", [hex: :poison, optional: false]}]},
"stripity_stripe": {:hex, :stripity_stripe, "2.0.0-alpha.5", "ba6d4ffc6251029135c76e9c6e2dd77580713f5c6833fb82da708336023bbfa2", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, optional: false]}, {:poison, "~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"timber": {:hex, :timber, "0.4.7", "df3fcd79bcb4eb4b53874d906ef5f3a212937b4bc7b7c5b244745202cc389443", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: true]}, {:phoenix, "~> 1.2", [hex: :phoenix, optional: true]}, {:plug, "~> 1.2", [hex: :plug, optional: true]}, {:poison, "~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
Expand Down
66 changes: 66 additions & 0 deletions priv/repo/migrations/20161209192504_create_task_list.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
defmodule CodeCorps.Repo.Migrations.CreateTaskList do
use Ecto.Migration
import Ecto.Changeset
import Ecto.Query

alias CodeCorps.Project
alias CodeCorps.Repo
alias CodeCorps.Task
alias CodeCorps.TaskList

def change do
create table(:task_lists) do
add :name, :string
add :order, :integer
add :project_id, references(:projects, on_delete: :nothing)

timestamps()
end

create index(:task_lists, [:project_id])

alter table(:tasks) do
add :task_list_id, references(:task_lists, on_delete: :nothing)
add :order, :integer
end

flush

Application.ensure_all_started :timex
migrate_existing()
end

def migrate_existing() do
Project
|> preload(:task_lists)
|> Repo.all()
|> Enum.each(&handle_project_migration/1)
end

defp handle_project_migration(project) do
cond do
project.task_lists != [] ->
IO.puts "Task lists already exist for #{project.title}, skipping migration."
true ->
IO.puts "Generating default task lists for #{project.title}."

{:ok, project} = Project.changeset(project, %{})
|> put_assoc(:task_lists, TaskList.default_task_lists())
|> Repo.update

add_existing_tasks_to_inbox(project, hd(project.task_lists))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is hd()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hd/1 refers to the first item in the list, the head. (Also, tl/1 refers to the last item, the tail)

end
end

defp add_existing_tasks_to_inbox(project, task_list) do
Task
|> CodeCorps.Helpers.Query.project_filter(%{ project_id: project.id })
|> Repo.all()
|> Enum.each(&assign_task_to_inbox(&1, task_list))
end

defp assign_task_to_inbox(task, task_list) do
Task.changeset(task, %{ task_list_id: task_list.id })
|> Repo.update()
end
end
3 changes: 2 additions & 1 deletion priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ cond do
status: "open",
number: i,
project_id: 1,
user_id: 1
user_id: 1,
task_list_id: 1
})
|> Repo.insert!
end
Expand Down
4 changes: 3 additions & 1 deletion test/controllers/project_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ defmodule CodeCorps.ProjectControllerTest do
organization = insert(:organization)
insert(:organization_membership, role: "admin", member: current_user, organization: organization)
attrs = @valid_attrs |> Map.merge(%{organization: organization})
assert conn |> request_create(attrs) |> json_response(201)
response = conn |> request_create(attrs)
assert %{assigns: %{data: %{task_lists: [_inbox, _backlog, _in_progress, _done]}}} = response
assert response |> json_response(201)
end

@tag authenticated: :admin
Expand Down
11 changes: 6 additions & 5 deletions test/controllers/task_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ defmodule CodeCorps.TaskControllerTest do
|> assert_ids_from_response([task_1.id, task_2.id])
end

test "lists all entries newest first", %{conn: conn} do
test "lists all entries, ordered correctly", %{conn: conn} do
# Has to be done manually. Inserting as a list is too quick.
# Field lacks the resolution to differentiate.
task_1 = insert(:task, inserted_at: Timex.to_date({2000, 1, 1}))
task_2 = insert(:task, inserted_at: Timex.to_date({2000, 1, 2}))
task_3 = insert(:task, inserted_at: Timex.to_date({2000, 1, 3}))
task_1 = insert(:task, order: 3000)
task_2 = insert(:task, order: 2000)
task_3 = insert(:task, order: 1000)

path = conn |> task_path(:index)
json = conn |> get(path) |> json_response(200)
Expand Down Expand Up @@ -138,7 +138,8 @@ defmodule CodeCorps.TaskControllerTest do
@tag :authenticated
test "creates and renders resource when data is valid", %{conn: conn, current_user: current_user} do
project = insert(:project)
attrs = @valid_attrs |> Map.merge(%{project: project, user: current_user})
task_list = insert(:task_list, project: project)
attrs = @valid_attrs |> Map.merge(%{project: project, user: current_user, task_list: task_list})
json = conn |> request_create(attrs) |> json_response(201)

# ensure record is reloaded from database before serialized, since number is added
Expand Down
79 changes: 79 additions & 0 deletions test/controllers/task_list_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule CodeCorps.TaskListControllerTest do
use CodeCorps.ApiCase, resource_name: :task_list

@valid_attrs %{
name: "Test task"
}

@invalid_attrs %{
name: nil
}

describe "index" do
test "lists all entries", %{conn: conn} do
[task_list_1, task_list_2] = insert_pair(:task_list)

conn
|> request_index
|> json_response(200)
|> assert_ids_from_response([task_list_1.id, task_list_2.id])
end

test "lists all entries by order", %{conn: conn} do
# Has to be done manually. Inserting as a list is too quick.
# Field lacks the resolution to differentiate.
project = insert(:project)
task_list_1 = insert(:task_list, project: project, order: 2000)
task_list_2 = insert(:task_list, project: project, order: 1000)
task_list_3 = insert(:task_list, project: project, order: 3000)

path = conn |> task_list_path(:index)

conn
|> get(path)
|> json_response(200)
|> assert_ids_from_response([task_list_2.id, task_list_1.id, task_list_3.id])
end

test "lists all task lists for a project", %{conn: conn} do
project_1 = insert(:project)
project_2 = insert(:project)
insert(:task_list, project: project_1)
insert(:task_list, project: project_1)
insert(:task_list, project: project_2)

json =
conn
|> get("projects/#{project_1.id}/task-lists")
|> json_response(200)

assert json["data"] |> Enum.count == 2
end
end

describe "show" do
test "shows chosen resource", %{conn: conn} do
task_list = insert(:task_list)

conn
|> request_show(task_list)
|> json_response(200)
|> Map.get("data")
|> assert_result_id(task_list.id)
end

test "shows task list by id for project", %{conn: conn} do
task_list = insert(:task_list)

path = conn |> project_task_list_path(:show, task_list.project_id, task_list.id)
data = conn |> get(path) |> json_response(200) |> Map.get("data")

assert data["id"] == "#{task_list.id}"
assert data["type"] == "task-list"
end

test "renders 404 when id is nonexistent", %{conn: conn} do
assert conn |> request_show(:not_found) |> json_response(404)
end
end
end
18 changes: 18 additions & 0 deletions test/models/task_list_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule CodeCorps.TaskListTest do
use CodeCorps.ModelCase

alias CodeCorps.TaskList

@valid_attrs %{name: "some content", position: 42}
@invalid_attrs %{}

test "changeset with valid attributes" do
changeset = TaskList.changeset(%TaskList{}, @valid_attrs)
assert changeset.valid?
end

test "changeset with invalid attributes" do
changeset = TaskList.changeset(%TaskList{}, @invalid_attrs)
refute changeset.valid?
end
end
51 changes: 46 additions & 5 deletions test/models/task_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ defmodule CodeCorps.TaskTest do
test "is valid with valid attributes" do
user = insert(:user)
project = insert(:project)
task_list = insert(:task_list)
changeset = Task.create_changeset(%Task{}, %{
markdown: "some content",
task_type: "issue",
title: "some content",
project_id: project.id,
user_id: user.id,
task_list_id: task_list.id
})
assert changeset.valid?
end
Expand All @@ -56,29 +58,68 @@ defmodule CodeCorps.TaskTest do
user = insert(:user)
project_a = insert(:project, title: "Project A")
project_b = insert(:project, title: "Project B")
task_list_a = insert(:task_list, name: "Task List A", project: project_a)
task_list_b = insert(:task_list, name: "Task List B", project: project_b)

insert(:task, project: project_a, user: user, title: "Project A Task 1")
insert(:task, project: project_a, user: user, title: "Project A Task 2")
insert(:task, project: project_a, user: user, task_list: task_list_a, order: 2000, title: "Project A Task 1")
insert(:task, project: project_a, user: user, task_list: task_list_a, order: 1000, title: "Project A Task 2")

insert(:task, project: project_b, user: user, title: "Project B Task 1")
insert(:task, project: project_b, user: user, task_list: task_list_b, title: "Project B Task 1")

changes = Map.merge(@valid_attrs, %{
project_id: project_a.id,
user_id: user.id
user_id: user.id,
task_list_id: task_list_a.id
})
changeset = Task.create_changeset(%Task{}, changes)
{:ok, result} = Repo.insert(changeset)
assert result.number == 3

changes = Map.merge(@valid_attrs, %{
project_id: project_b.id,
user_id: user.id
user_id: user.id,
task_list_id: task_list_b.id
})
changeset = Task.create_changeset(%Task{}, changes)
{:ok, result} = Repo.insert(changeset)
assert result.number == 2
end

test "auto-assigns order, beginning of list, scoped to task list" do
user = insert(:user)
project_a = insert(:project, title: "Project A")
task_list_a = insert(:task_list, name: "Task List A", project: project_a)
task_list_b = insert(:task_list, name: "Task List B", project: project_a)

task_a_1 = insert(:task, project: project_a, user: user, task_list: task_list_a, order: 2000, title: "Project A Task 1")
task_a_2 = insert(:task, project: project_a, user: user, task_list: task_list_a, order: 1000, title: "Project A Task 2")

task_b_1 = insert(:task, project: project_a, user: user, task_list: task_list_b, order: 2000, title: "Project B Task 1")
task_b_2 = insert(:task, project: project_a, user: user, task_list: task_list_b, order: 1000, title: "Project B Task 2")

changes = Map.merge(@valid_attrs, %{
project_id: project_a.id,
user_id: user.id,
task_list_id: task_list_a.id
})
changeset = Task.create_changeset(%Task{}, changes)
{:ok, result_a} = Repo.insert(changeset)
assert result_a.order < task_a_1.order && result_a.order < task_a_2.order

changes = Map.merge(@valid_attrs, %{
project_id: project_a.id,
user_id: user.id,
task_list_id: task_list_b.id
})
changeset = Task.create_changeset(%Task{}, changes)
{:ok, result_b} = Repo.insert(changeset)
assert result_b.order < task_b_1.order && result_b.order < task_b_2.order

# Make sure that, given the same order configuration between task lists,
# the auto-assigned order is the same, meaning the order is correctly scoped
assert result_a.order == result_b.order
end

test "sets state to 'published'" do
changeset = Task.create_changeset(%Task{}, %{})
assert changeset |> get_change(:state) == "published"
Expand Down
Loading