Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PR] Edit timer #196

Merged
merged 55 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e3b9165
feat: Example UI done.
LuchoTurtle Oct 27, 2022
38e4e32
feat: Adding timer field to item when fetching.
LuchoTurtle Oct 27, 2022
8606526
fix: Removing nil timers.
LuchoTurtle Oct 27, 2022
cdae413
fix: Returning truncated format to LIveview.
LuchoTurtle Oct 27, 2022
57f7911
feat: Finally passing data to server.
LuchoTurtle Oct 28, 2022
3556726
feat: Updating timer info on repo. Fixing IDs.
LuchoTurtle Oct 28, 2022
9fefc71
fix: Fixing when no timers are found.
LuchoTurtle Oct 28, 2022
5c0a97d
fix: Error handling and fixing format issues on CI.
LuchoTurtle Oct 31, 2022
7cb2e9b
fix: Fixing formatting errors.
LuchoTurtle Oct 31, 2022
233b6de
feat: Adding tests for DateTimeParser.
LuchoTurtle Oct 31, 2022
c242dfd
feat: Increasing code coverage.
LuchoTurtle Oct 31, 2022
909918b
feat: Adding docs to DateTimeParser.
LuchoTurtle Oct 31, 2022
38c0d42
fix: Formatting test files.
LuchoTurtle Oct 31, 2022
24d15f9
fix: Fixing test.
LuchoTurtle Oct 31, 2022
684101c
fix: Fixing unwanted commit.
LuchoTurtle Oct 31, 2022
ffd1c72
fix: Fixing broadcast bug.
LuchoTurtle Nov 2, 2022
ecc2ef5
fix: Fixing introduced bug on saving item.
LuchoTurtle Nov 2, 2022
c1fd3ea
feat: Updating BUILD.MD.
LuchoTurtle Nov 3, 2022
17ca24b
feat: Adding timer changeset array to socket assigns and erroring thr…
LuchoTurtle Nov 4, 2022
211a93c
feat: Removing unnecessary list of timers when fetching items on mount.
LuchoTurtle Nov 4, 2022
45df2a1
fix: Fixing duplicate form when error occurs.
LuchoTurtle Nov 4, 2022
db5e040
feat: Creating method to error out timer changeset.
LuchoTurtle Nov 5, 2022
8b09799
fix: Fixing tests.
LuchoTurtle Nov 5, 2022
b255561
feat: Add remaining update tests.
LuchoTurtle Nov 5, 2022
d0789f4
Merge branch 'array_of_changesets' into edit-timer
LuchoTurtle Nov 5, 2022
b538b86
fix: Fixing format.
LuchoTurtle Nov 5, 2022
d8360f7
feat: Updating timer.
LuchoTurtle Nov 7, 2022
2642334
fix: Fixing warnings.
LuchoTurtle Nov 8, 2022
d601dc0
fix: Format code.
LuchoTurtle Nov 8, 2022
0f0b813
update table of contents in BUILDIT.md as per https://github.com/dwyl…
nelsonic Nov 9, 2022
e125fae
fix: Errors are cleared on another submit. #195.
LuchoTurtle Nov 10, 2022
2f9bc89
feat: Checking if timer being edited overlaps with others #195
LuchoTurtle Nov 10, 2022
d2c2b91
fix: Uncommenting update timer line.
LuchoTurtle Nov 10, 2022
ac53751
fix: Editing timers while running works properly. #195
LuchoTurtle Nov 11, 2022
1bcdaca
feat: Adding tests to test behaviour when editing timers. #195
LuchoTurtle Nov 11, 2022
76c0a50
fix: Formatting files. #195
LuchoTurtle Nov 11, 2022
b2171d7
fix: Fixing auth test. #195
LuchoTurtle Nov 11, 2022
356d69a
fix: Format test file.
LuchoTurtle Nov 11, 2022
8d7a449
fix: Deleting parser in favour of using Timex.
LuchoTurtle Nov 14, 2022
be0ba43
fix: Refactoring erroring timer changeset to Timer file.
LuchoTurtle Nov 14, 2022
5f70ee9
fix: Refactoring changeset code to timer.ex.
LuchoTurtle Nov 14, 2022
0e5a49e
fix: Refactoring erroring changeset in Timer.ex. #195
LuchoTurtle Nov 14, 2022
6536a5f
fix: Fixing tests on ongoing timers and refactoring to Timer.ex. #195
LuchoTurtle Nov 14, 2022
71d2f4e
fix: Adding documentation and simplifying controller pattern matching…
LuchoTurtle Nov 14, 2022
1b62bd7
fix: Updating BUILD.md file. #195
LuchoTurtle Nov 14, 2022
10f400b
fix: Formatting files.
LuchoTurtle Nov 14, 2022
358d85a
fix: Commenting adding Timex as dependency.
LuchoTurtle Nov 15, 2022
71592db
fix: Fixing test warning on "handle_info/2 update with editing open".
LuchoTurtle Nov 15, 2022
e4b7f4c
Merge remote-tracking branch 'origin/edit-timer' into edit-timer
LuchoTurtle Nov 15, 2022
60ede76
fix: Pattern matching on `update_timer_inside_changeset_list`.
LuchoTurtle Nov 15, 2022
677f68d
fix: Fixing editing historical timer while there is an ongoing timer.
LuchoTurtle Nov 19, 2022
042a6ce
fix: Formatting files.
LuchoTurtle Nov 19, 2022
8225674
fix: Updating BUILD.md file.
LuchoTurtle Nov 19, 2022
53b7de6
fix: Fix tests for 100% coverage.
LuchoTurtle Nov 21, 2022
38b26ae
remove "environment: dwylauth" from ci.yml
nelsonic Nov 21, 2022
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ npm-debug.log
.env

#elm
elm-stuff/
elm-stuff/
LuchoTurtle marked this conversation as resolved.
Show resolved Hide resolved
.vscode/
192 changes: 192 additions & 0 deletions lib/app/datetime_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Full credit of this module goes to https://dev.to/onpointvn/build-your-own-date-time-parser-in-elixir-50be
# Do check the gist -> https://gist.github.com/bluzky/62a20cdb57b17f47c67261c10aa3da8b
defmodule DateTimeParser do
@mapping %{
"H" => "(?<hour>\\d{2})",
"I" => "(?<hour12>\\d{2})",
"M" => "(?<minute>\\d{2})",
"S" => "(?<second>\\d{2})",
"d" => "(?<day>\\d{2})",
"m" => "(?<month>\\d{2})",
"y" => "(?<year2>\\d{2})",
"Y" => "(?<year>-?\\d{4})",
"z" => "(?<tz>[+-]?\\d{4})",
"Z" => "(?<tz_name>[a-zA-Z_\/]+)",
"p" => "(?<p>PM|AM)",
"P" => "(?<P>pm|am)",
"%" => "%"
}

@doc """
Parse string to datetime struct
**Example**
parse("2021-20-10", "%Y-%M-%d")
Support format
| format | description| value example |
| -- | -- | -- |
| H | 24 hour | 00 - 23 |
| I | 12 hour | 00 - 12 |
| M | minute| 00 - 59 |
| S | second | 00 - 59 |
| d | day | 01 - 31 |
| m | month | 01 -12 |
| y | 2 digits year | 00 - 99 |
| Y | 4 digits year | |
| z | timezone offset | +0100, -0330 |
| Z | timezone name | UTC+7, Asia/Ho_Chi_Minh |
| p | PM or AM | |
| P | pm or am | |
"""
def parse!(dt_string, format \\ "%Y-%m-%dT%H:%M:%SZ") do
case parse(dt_string, format) do
{:ok, dt} -> dt
{:error, message} -> raise "Parse string #{dt_string} with error: #{message}"
end
end

def parse(dt_string, format \\ "%Y-%m-%dT%H:%M:%SZ") do
format
|> build_regex
|> Regex.named_captures(dt_string)
|> cast_data
|> to_datetime
end

def build_regex(format) do
keys = Map.keys(@mapping) |> Enum.join("")

Regex.compile!("([^%]*)%([#{keys}])([^%]*)")
|> Regex.scan(format)
|> Enum.map(fn [_, s1, key, s2] ->
[s1, Map.get(@mapping, key), s2]
end)
|> to_string()
|> Regex.compile!()
end

@default_value %{
day: 1,
month: 1,
year: 0,
hour: 0,
minute: 0,
second: 0,
utc_offset: 0,
tz_name: "UTC",
shift: "AM"
}
def cast_data(nil), do: {:error, "invalid datetime"}

def cast_data(captures) do
captures
|> Enum.reduce_while([], fn {part, value}, acc ->
case cast(part, value) do
{:ok, data} -> {:cont, [data | acc]}
{:error, _} = error -> {:halt, error}
end
end)
|> case do
{:error, _} = error -> error
data -> Enum.into(data, @default_value)
end
end

@value_rages %{
"hour" => [0, 23],
"hour12" => [0, 12],
"minute" => [0, 59],
"second" => [0, 59],
"day" => [0, 31],
"month" => [1, 12],
"year2" => [0, 99]
}

defp cast("P", value) do
cast("p", String.upcase(value))
end

defp cast("p", value) do
{:ok, {:shift, value}}
end

defp cast("tz", value) do
{hour, minute} = String.split_at(value, 3)

with {:ok, {_, hour}} <- cast("offset_h", hour),
{:ok, {_, minute}} <- cast("offset_m", minute) do
sign = div(hour, abs(hour))
{:ok, {:utc_offset, sign * (abs(hour) * 3600 + minute * 60)}}
else
_ -> {:error, "#{value} is invalid timezone offset"}
end
end

defp cast("tz_name", value) do
{:ok, {:tz_name, value}}
end

defp cast(part, value) do
value = String.to_integer(value)

valid =
case Map.get(@value_rages, part) do
[min, max] ->
value >= min and value <= max

_ ->
true
end

if valid do
{:ok, {String.to_atom(part), value}}
else
{:error, "#{value} is not a valid #{part}"}
end
end

defp to_datetime({:error, _} = error), do: error

defp to_datetime(%{year2: value} = data) do
current_year = DateTime.utc_now() |> Map.get(:year)
year = div(current_year, 100) * 100 + value

data
|> Map.put(:year, year)
|> Map.delete(:year2)
|> to_datetime()
end

defp to_datetime(%{hour12: hour} = data) do
# 12AM is not valid

if hour == 12 and data.shift == "AM" do
{:error, "12AM is invalid value"}
else
hour =
cond do
hour == 12 and data.shift == "PM" -> hour
data.shift == "AM" -> hour
data.shift == "PM" -> hour + 12
end

data
|> Map.put(:hour, hour)
|> Map.delete(:hour12)
|> to_datetime()
end
end

defp to_datetime(data) do
with {:ok, date} <- Date.new(data.year, data.month, data.day),
{:ok, time} <- Time.new(data.hour, data.minute, data.second),
{:ok, datetime} <- DateTime.new(date, time) do
datetime = DateTime.add(datetime, -data.utc_offset, :second)

if data.tz_name != "UTC" do
DateTime.shift_zone(datetime, data.tz_name)
else
{:ok, datetime}
end
end
end
end
11 changes: 11 additions & 0 deletions lib/app/item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule App.Item do
import Ecto.Query
alias App.{Repo, Tag, ItemTag, Person}
alias __MODULE__
require Logger

schema "items" do
field :status, :integer
Expand Down Expand Up @@ -159,9 +160,19 @@ defmodule App.Item do
list_person_items(person_id)
|> Enum.reduce(%{}, fn i, acc -> Map.put(acc, i.id, i) end)

items_timers =
Enum.group_by(values,
fn row -> row.id end,
fn obj ->
start = if obj.start != nil, do: NaiveDateTime.truncate(obj.start, :second) |> NaiveDateTime.to_string(), else: nil
stop = if obj.stop != nil, do: NaiveDateTime.truncate(obj.stop, :second) |> NaiveDateTime.to_string(), else: nil
%{start: start, stop: stop, id: obj.timer_id}
end)

accumulate_item_timers(values)
|> Enum.map(fn t ->
Map.put(t, :tags, items_tags[t.id].tags)
|> Map.put(:timers, Enum.reject(items_timers[t.id], fn %{start: start, stop: stop} -> start == nil and stop == nil end ))
end)
end

Expand Down
16 changes: 16 additions & 0 deletions lib/app/timer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ defmodule App.Timer do
|> Repo.update()
end

@doc """
Updates a timer object.

## Examples

iex> update_timer(%{id: 1, start: ~N[2022-07-11 05:15:31], stop: ~N[2022-07-11 05:15:37]})
{:ok, %Timer{id: 1, start: ~N[2022-07-11 05:15:31], stop: ~N[2022-07-11 05:15:37}}

"""
def update_timer(attrs \\ %{}) do
get_timer!(attrs.id)
|> changeset(attrs)
|> Repo.update()
end


@doc """
`stop_timer_for_item_id/1` stops a timer for the given item_id if there is one.
Fails silently if there is no timer for the given item_id.
Expand Down
31 changes: 30 additions & 1 deletion lib/app_web/live/app_live.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
defmodule AppWeb.AppLive do
require Logger

use AppWeb, :live_view
alias App.{Item, Timer}
# run authentication on mount
on_mount AppWeb.AuthController
alias Phoenix.Socket.Broadcast



@topic "live"

defp get_person_id(assigns), do: assigns[:person][:id] || 0
Expand Down Expand Up @@ -111,6 +115,31 @@ defmodule AppWeb.AppLive do
{:noreply, assign(socket, editing: nil)}
end

@impl true
def handle_event("update-item-timer",
%{"id" => id, "timer_start" => timer_start, "timer_stop" => timer_stop},
socket) do

try do
LuchoTurtle marked this conversation as resolved.
Show resolved Hide resolved
start = DateTimeParser.parse!(timer_start, "%Y-%m-%d %H:%M:%S")
stop = DateTimeParser.parse!(timer_stop, "%Y-%m-%d %H:%M:%S")

bruh = DateTime.compare(start, stop)

case DateTime.compare(start, stop) do
LuchoTurtle marked this conversation as resolved.
Show resolved Hide resolved
:lt -> Timer.update_timer(%{ id: id, start: start, stop: stop })
:eq -> Logger.debug("dates are the same")
:gt -> Logger.debug("Start is newer that stop")
end

rescue
e -> Logger.debug("Date format invalid on either start or stop, #{inspect(e)}")
end

#AppWeb.Endpoint.broadcast(@topic, "update", :update)
{:noreply, socket}
end

@impl true
def handle_info(%Broadcast{event: "update", payload: _message}, socket) do
person_id = get_person_id(socket.assigns)
Expand Down Expand Up @@ -245,7 +274,7 @@ defmodule AppWeb.AppLive do
the tag names are seperated by commas

## Examples

tags_to_string([%Tag{text: "Learn"}, %Tag{text: "Elixir"}])
"Learn, Elixir"

Expand Down
Loading