From b75de75231d67e2b8770681a13a49e7468982ad1 Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Sun, 15 Dec 2024 14:09:47 +0000 Subject: [PATCH 1/5] WIP: Manually set clockin time Partially implemented clocked out case --- src/tanda_cli/commands/clock_in.cr | 1 + src/tanda_cli/commands/clock_in/manual.cr | 70 +++++++++++++++++++++++ src/tanda_cli/executors/clock_in.cr | 19 +----- src/tanda_cli/models/clock_in_status.cr | 32 +++++++++++ src/tanda_cli/utils/time.cr | 16 ++++++ 5 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/tanda_cli/commands/clock_in/manual.cr create mode 100644 src/tanda_cli/models/clock_in_status.cr diff --git a/src/tanda_cli/commands/clock_in.cr b/src/tanda_cli/commands/clock_in.cr index 5a20c262..0b1412a4 100644 --- a/src/tanda_cli/commands/clock_in.cr +++ b/src/tanda_cli/commands/clock_in.cr @@ -38,6 +38,7 @@ module TandaCLI add_commands( ClockIn::Start.new, ClockIn::Finish.new, + ClockIn::Manual.new, ClockIn::Break.new, ClockIn::Photo.new, ClockIn::Status.new, diff --git a/src/tanda_cli/commands/clock_in/manual.cr b/src/tanda_cli/commands/clock_in/manual.cr new file mode 100644 index 00000000..55b466a1 --- /dev/null +++ b/src/tanda_cli/commands/clock_in/manual.cr @@ -0,0 +1,70 @@ +require "../../client_builder" +require "../../models/clock_in_status" + +module TandaCLI + module Commands + class ClockIn + class Manual < Commands::Base + include ClientBuilder + + required_scopes :timesheet + + def setup_ + @name = "manual" + @summary = @description = "Manually set shift time for clockin" + end + + def run_(arguments : Cling::Arguments, options : Cling::Options) : Nil + shifts = client.todays_shifts.or(&.display!) + status = Models::ClockInStatus.new(shifts).determine_status + + case status + in .clocked_out? + handle_clocked_out! + in .clocked_in? + puts "Clocked in" + in .break_started? + puts "Break started" + end + end + + private def handle_clocked_out! + message = "You are currently clocked out, what do you want to set as the clock in time?" + time = ask_for_and_parse_time!(message) + + # TODO: Actually create shift and set time + Utils::Display.success("Set clock in time to #{time}") + end + + private def ask_for_and_parse_time!(message) : Time + time_string = Utils::Input.request(message) + Utils::Display.error!("Input can't be blank!") if time_string.nil? + + time = Utils::Time.parse?(time_string).try(&->time_to_day_time(Time)) + Utils::Display.error!("#{time_string} is not a valid time!") if time.nil? + + puts time + Utils::Input.request_and(message: "Is this correct?") do |input| + Utils::Display.error!("Command aborted. Please try again.") if input != "y" + end + + time + end + + private def time_to_day_time(time : Time) : Time + now = Utils::Time.now + + Time.local( + now.year, + now.month, + now.day, + time.hour, + time.minute, + time.second, + location: now.location + ) + end + end + end + end +end diff --git a/src/tanda_cli/executors/clock_in.cr b/src/tanda_cli/executors/clock_in.cr index f930299b..bb52f453 100644 --- a/src/tanda_cli/executors/clock_in.cr +++ b/src/tanda_cli/executors/clock_in.cr @@ -1,3 +1,4 @@ +require "../models/clock_in_status" require "../models/photo_path_parser" module TandaCLI @@ -99,22 +100,8 @@ module TandaCLI BreakStarted end - private def determine_status : ClockInStatus - if break_started? - ClockInStatus::BreakStarted - elsif clocked_in? - ClockInStatus::ClockedIn - else - ClockInStatus::ClockedOut - end - end - - private def break_started? : Bool - @shifts.any?(&.ongoing_break?) - end - - private def clocked_in? : Bool - @shifts.any? { |shift| shift.start_time && shift.finish_time.nil? } + private def determine_status : Models::ClockInStatus::Status + Models::ClockInStatus.new(@shifts).determine_status end private def validate_clockin_start! diff --git a/src/tanda_cli/models/clock_in_status.cr b/src/tanda_cli/models/clock_in_status.cr new file mode 100644 index 00000000..7fbe35d7 --- /dev/null +++ b/src/tanda_cli/models/clock_in_status.cr @@ -0,0 +1,32 @@ +module TandaCLI + module Models + struct ClockInStatus + enum Status + ClockedIn + ClockedOut + BreakStarted + end + + def initialize(@shifts : Array(Types::Shift)); end + + def determine_status : Status + if break_started? + Status::BreakStarted + elsif clocked_in? + Status::ClockedIn + else + Status::ClockedOut + end + end + + private def break_started? : Bool + @shifts.any?(&.ongoing_break?) + end + + private def clocked_in? : Bool + @shifts.any? { |shift| shift.start_time && shift.finish_time.nil? } + end + end + end +end + diff --git a/src/tanda_cli/utils/time.cr b/src/tanda_cli/utils/time.cr index 352e4486..2e7fde29 100644 --- a/src/tanda_cli/utils/time.cr +++ b/src/tanda_cli/utils/time.cr @@ -31,6 +31,22 @@ module TandaCLI def iso_date(date : String) : ::Time ::Time.parse(date, ISO_DATE, location: Current.time_zone) end + + def parse?(time_string : String) ::Time? + time_zone = Current.time_zone + formats = { + "%l%P", # "9am", "12pm" + "%l:%M%P" # "1:30pm" + } + + formats + .each + .map do |format| + ::Time.parse(time_string, format, time_zone) + rescue ::Time::Format::Error + end + .find { |t| !t.nil? } + end end end end From 89c2dca54f113872b8a3d336c3d32ca889b75928 Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Mon, 16 Dec 2024 08:22:01 +0000 Subject: [PATCH 2/5] Fix issue with return type + format --- src/tanda_cli/models/clock_in_status.cr | 1 - src/tanda_cli/utils/time.cr | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tanda_cli/models/clock_in_status.cr b/src/tanda_cli/models/clock_in_status.cr index 7fbe35d7..1a84fa55 100644 --- a/src/tanda_cli/models/clock_in_status.cr +++ b/src/tanda_cli/models/clock_in_status.cr @@ -29,4 +29,3 @@ module TandaCLI end end end - diff --git a/src/tanda_cli/utils/time.cr b/src/tanda_cli/utils/time.cr index 2e7fde29..d02cf776 100644 --- a/src/tanda_cli/utils/time.cr +++ b/src/tanda_cli/utils/time.cr @@ -32,11 +32,11 @@ module TandaCLI ::Time.parse(date, ISO_DATE, location: Current.time_zone) end - def parse?(time_string : String) ::Time? + def parse?(time_string : String) : ::Time? time_zone = Current.time_zone formats = { - "%l%P", # "9am", "12pm" - "%l:%M%P" # "1:30pm" + "%l%P", # "9am", "12pm" + "%l:%M%P", # "1:30pm" } formats From 9a6c41be7b79f53c00ea2d679fc826a4c97e30c7 Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Fri, 27 Dec 2024 20:25:22 +0000 Subject: [PATCH 3/5] Lint --- src/tanda_cli/utils/time.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tanda_cli/utils/time.cr b/src/tanda_cli/utils/time.cr index d49c2627..3c88ea37 100644 --- a/src/tanda_cli/utils/time.cr +++ b/src/tanda_cli/utils/time.cr @@ -48,7 +48,7 @@ module TandaCLI ::Time.parse(time_string, format, location) rescue ::Time::Format::Error end - .find { |t| !t.nil? } + .find(&.itself) end end end From 16a3b8aeffd8f4b3104c0a2a2b2267883e24d95e Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Sat, 28 Dec 2024 13:05:39 +0000 Subject: [PATCH 4/5] Fix input not using stdin + add tests --- spec/commands/clock_in/manual_spec.cr | 56 +++++++++++++++++++++++ spec/spec_helper.cr | 10 ++++ src/tanda_cli/commands/clock_in/manual.cr | 3 +- src/tanda_cli/input.cr | 7 ++- 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 spec/commands/clock_in/manual_spec.cr diff --git a/spec/commands/clock_in/manual_spec.cr b/spec/commands/clock_in/manual_spec.cr new file mode 100644 index 00000000..2cea671b --- /dev/null +++ b/spec/commands/clock_in/manual_spec.cr @@ -0,0 +1,56 @@ +require "../../spec_helper" + +describe TandaCLI::Commands::ClockIn::Manual do + it "Handles clocked out state asking for a time to clock in with" do + stub_shifts + + travel_to(Time.local(2024, 12, 28, 9)) + + stdin = build_stdin( + "9:00am", + "y" + ) + context = run(stdin) + + output = context.stdout.to_s + output.should contain("You are currently clocked out, what do you want to set as the clock in time?\n") + output.should contain("Is this correct?\n") + output.should contain("Success: Set clock in time to 2024-12-28 09:00:00 +00:00\n") + end + + it "Handles blank time input" do + stub_shifts + + stdin = build_stdin("") + context = run(stdin) + + output = context.stdout.to_s + output.should contain("You are currently clocked out, what do you want to set as the clock in time?\n") + output.should contain("Error: Input can't be blank!") + end + + it "Handles invalid time input" do + stub_shifts + + time_string = "invalid time" + stdin = build_stdin(time_string) + context = run(stdin) + + output = context.stdout.to_s + output.should contain("You are currently clocked out, what do you want to set as the clock in time?\n") + output.should contain("Error: \"#{time_string}\" is not a valid time!\n") + end +end + +private def run(stdin) + Command.run(["clockin", "manual"], stdin: stdin) +end + +private def stub_shifts(shifts = Array(NamedTuple()).new) + WebMock + .stub(:get, endpoint(Regex.new("/shifts"))) + .to_return( + status: 200, + body: shifts.to_json, + ) +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 897b2f41..320653da 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -30,3 +30,13 @@ end def endpoint(regex : Regex) Regex.new("#{DEFAULT_BASE_URI}#{regex}") end + +def build_stdin(*lines : String) : IO + IO::Memory.new.tap do |stdin| + lines.each do |line| + stdin.puts line + end + + stdin.rewind + end +end diff --git a/src/tanda_cli/commands/clock_in/manual.cr b/src/tanda_cli/commands/clock_in/manual.cr index 2d8ee89c..85f7789b 100644 --- a/src/tanda_cli/commands/clock_in/manual.cr +++ b/src/tanda_cli/commands/clock_in/manual.cr @@ -38,9 +38,8 @@ module TandaCLI display.error!("Input can't be blank!") if time_string.nil? time = Utils::Time.parse?(time_string).try(&->time_to_day_time(Time)) - display.error!("#{time_string} is not a valid time!") if time.nil? + display.error!("\"#{time_string}\" is not a valid time!") if time.nil? - puts time input.request_and(message: "Is this correct?") do |user_input| display.error!("Command aborted. Please try again.") if user_input != "y" end diff --git a/src/tanda_cli/input.cr b/src/tanda_cli/input.cr index 7fbdc700..de7ba3ca 100644 --- a/src/tanda_cli/input.cr +++ b/src/tanda_cli/input.cr @@ -21,7 +21,7 @@ module TandaCLI end retrieve_input(sensitive) do - gets.try(&.chomp).presence + @stdin.gets(chomp: true).presence end end @@ -36,7 +36,10 @@ module TandaCLI private def retrieve_input(sensitive : Bool, & : -> String?) : String? return yield unless sensitive - STDIN.noecho do + stdin = @stdin + return yield unless stdin.is_a?(IO::FileDescriptor) + + stdin.noecho do yield end end From 3a00b32279e86ed6d8d5065db5e8b3470e610eb0 Mon Sep 17 00:00:00 2001 From: Daniel Gilchrist Date: Sat, 28 Dec 2024 13:07:10 +0000 Subject: [PATCH 5/5] Make blank spec also check for spaces in input --- spec/commands/clock_in/manual_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/commands/clock_in/manual_spec.cr b/spec/commands/clock_in/manual_spec.cr index 2cea671b..d43f0518 100644 --- a/spec/commands/clock_in/manual_spec.cr +++ b/spec/commands/clock_in/manual_spec.cr @@ -21,7 +21,7 @@ describe TandaCLI::Commands::ClockIn::Manual do it "Handles blank time input" do stub_shifts - stdin = build_stdin("") + stdin = build_stdin(" ") context = run(stdin) output = context.stdout.to_s