Commit 2c466dee authored by Sean McGivern's avatar Sean McGivern

Merge branch '1312-time-spent-at' into 'master'

Added possibility to enter past date in /spend command to log time in the past

See merge request gitlab-org/gitlab-ee!3044
parents fa0b1253 35f593be
...@@ -9,7 +9,7 @@ module TimeTrackable ...@@ -9,7 +9,7 @@ module TimeTrackable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
attr_reader :time_spent, :time_spent_user attr_reader :time_spent, :time_spent_user, :spent_at
alias_method :time_spent?, :time_spent alias_method :time_spent?, :time_spent
...@@ -24,6 +24,7 @@ module TimeTrackable ...@@ -24,6 +24,7 @@ module TimeTrackable
def spend_time(options) def spend_time(options)
@time_spent = options[:duration] @time_spent = options[:duration]
@time_spent_user = options[:user] @time_spent_user = options[:user]
@spent_at = options[:spent_at]
@original_total_time_spent = nil @original_total_time_spent = nil
return if @time_spent == 0 return if @time_spent == 0
...@@ -55,7 +56,11 @@ module TimeTrackable ...@@ -55,7 +56,11 @@ module TimeTrackable
end end
def add_or_subtract_spent_time def add_or_subtract_spent_time
timelogs.new(time_spent: time_spent, user: @time_spent_user) timelogs.new(
time_spent: time_spent,
user: @time_spent_user,
spent_at: @spent_at
)
end end
def check_negative_time_spent def check_negative_time_spent
......
...@@ -382,7 +382,7 @@ module QuickActions ...@@ -382,7 +382,7 @@ module QuickActions
end end
desc 'Add or substract spent time' desc 'Add or substract spent time'
explanation do |time_spent| explanation do |time_spent, time_spent_date|
if time_spent if time_spent
if time_spent > 0 if time_spent > 0
verb = 'Adds' verb = 'Adds'
...@@ -395,16 +395,20 @@ module QuickActions ...@@ -395,16 +395,20 @@ module QuickActions
"#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time." "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
end end
end end
params '<1h 30m | -1h 30m>' params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end end
parse_params do |raw_duration| parse_params do |raw_time_date|
Gitlab::TimeTrackingFormatter.parse(raw_duration) Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
end end
command :spend do |time_spent| command :spend do |time_spent, time_spent_date|
if time_spent if time_spent
@updates[:spend_time] = { duration: time_spent, user: current_user } @updates[:spend_time] = {
duration: time_spent,
user: current_user,
spent_at: time_spent_date
}
end end
end end
......
...@@ -195,9 +195,11 @@ module SystemNoteService ...@@ -195,9 +195,11 @@ module SystemNoteService
if time_spent == :reset if time_spent == :reset
body = "removed time spent" body = "removed time spent"
else else
spent_at = noteable.spent_at
parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
action = time_spent > 0 ? 'added' : 'subtracted' action = time_spent > 0 ? 'added' : 'subtracted'
body = "#{action} #{parsed_time} of time spent" body = "#{action} #{parsed_time} of time spent"
body << " at #{spent_at}" if spent_at
end end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
......
---
title: Added possibility to enter past date in /spend command to log time in the past
merge_request: 3044
author: g3dinua, LockiStrike
type: changed
class AddSpentAtToTimelogs < ActiveRecord::Migration
DOWNTIME = false
def up
add_column :timelogs, :spent_at, :datetime_with_timezone
end
def down
remove_column :timelogs, :spent_at
end
end
...@@ -1907,6 +1907,7 @@ ActiveRecord::Schema.define(version: 20171006091000) do ...@@ -1907,6 +1907,7 @@ ActiveRecord::Schema.define(version: 20171006091000) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "issue_id" t.integer "issue_id"
t.integer "merge_request_id" t.integer "merge_request_id"
t.datetime_with_timezone "spent_at"
end end
add_index "timelogs", ["issue_id"], name: "index_timelogs_on_issue_id", using: :btree add_index "timelogs", ["issue_id"], name: "index_timelogs_on_issue_id", using: :btree
......
...@@ -33,7 +33,7 @@ do. ...@@ -33,7 +33,7 @@ do.
| `/wip` | Toggle the Work In Progress status | | `/wip` | Toggle the Work In Progress status |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | | <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate |
| `/remove_estimate` | Remove estimated time | | `/remove_estimate` | Remove estimated time |
| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time | | <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on |
| `/remove_time_spent` | Remove time spent | | `/remove_time_spent` | Remove time spent |
| `/target_branch <Branch Name>` | Set target branch for current merge request | | `/target_branch <Branch Name>` | Set target branch for current merge request |
| `/award :emoji:` | Toggle award for :emoji: | | `/award :emoji:` | Toggle award for :emoji: |
......
module Gitlab
module QuickActions
# This class takes spend command argument
# and separates date and time from spend command arguments if it present
# example:
# spend_command_time_and_date = "15m 2017-01-02"
# SpendTimeAndDateSeparator.new(spend_command_time_and_date).execute
# => [900, Mon, 02 Jan 2017]
# if date doesn't present return time with current date
# in other cases return nil
class SpendTimeAndDateSeparator
DATE_REGEX = /(\d{2,4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})/
def initialize(spend_command_arg)
@spend_arg = spend_command_arg
end
def execute
return if @spend_arg.blank?
return [get_time, DateTime.now.to_date] unless date_present?
return unless valid_date?
[get_time, get_date]
end
private
def get_time
raw_time = @spend_arg.gsub(DATE_REGEX, '')
Gitlab::TimeTrackingFormatter.parse(raw_time)
end
def get_date
string_date = @spend_arg.match(DATE_REGEX)[0]
Date.parse(string_date)
end
def date_present?
DATE_REGEX =~ @spend_arg
end
def valid_date?
string_date = @spend_arg.match(DATE_REGEX)[0]
date = Date.parse(string_date) rescue nil
date_past_or_today?(date)
end
def date_past_or_today?(date)
date&.past? || date&.today?
end
end
end
end
...@@ -516,6 +516,7 @@ Timelog: ...@@ -516,6 +516,7 @@ Timelog:
- merge_request_id - merge_request_id
- issue_id - issue_id
- user_id - user_id
- spent_at
- created_at - created_at
- updated_at - updated_at
ProjectAutoDevops: ProjectAutoDevops:
......
require 'spec_helper'
describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
subject { described_class }
shared_examples 'arg line with invalid parameters' do
it 'return nil' do
expect(subject.new(invalid_arg).execute).to eq(nil)
end
end
shared_examples 'arg line with valid parameters' do
it 'return time and date array' do
expect(subject.new(valid_arg).execute).to eq(expected_response)
end
end
describe '#execute' do
context 'invalid paramenter in arg line' do
context 'empty arg line' do
it_behaves_like 'arg line with invalid parameters' do
let(:invalid_arg) { '' }
end
end
context 'future date in arg line' do
it_behaves_like 'arg line with invalid parameters' do
let(:invalid_arg) { '10m 6023-02-02' }
end
end
context 'unparseable date(invalid mixes of delimiters)' do
it_behaves_like 'arg line with invalid parameters' do
let(:invalid_arg) { '10m 2017.02-02' }
end
end
context 'trash in arg line' do
let(:invalid_arg) { 'dfjkghdskjfghdjskfgdfg' }
it 'return nil as time value' do
time_date_response = subject.new(invalid_arg).execute
expect(time_date_response).to be_an_instance_of(Array)
expect(time_date_response.first).to eq(nil)
end
end
end
context 'only time present in arg line' do
it_behaves_like 'arg line with valid parameters' do
let(:valid_arg) { '2m 3m 5m 1h' }
let(:time) { Gitlab::TimeTrackingFormatter.parse(valid_arg) }
let(:date) { DateTime.now.to_date }
let(:expected_response) { [time, date] }
end
end
context 'simple time with date in arg line' do
it_behaves_like 'arg line with valid parameters' do
let(:raw_time) { '10m' }
let(:raw_date) { '2016-02-02' }
let(:valid_arg) { "#{raw_time} #{raw_date}" }
let(:date) { Date.parse(raw_date) }
let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) }
let(:expected_response) { [time, date] }
end
end
context 'composite time with date in arg line' do
it_behaves_like 'arg line with valid parameters' do
let(:raw_time) { '2m 10m 1h 3d' }
let(:raw_date) { '2016/02/02' }
let(:valid_arg) { "#{raw_time} #{raw_date}" }
let(:date) { Date.parse(raw_date) }
let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) }
let(:expected_response) { [time, date] }
end
end
end
end
...@@ -209,7 +209,11 @@ describe QuickActions::InterpretService do ...@@ -209,7 +209,11 @@ describe QuickActions::InterpretService do
it 'populates spend_time: 3600 if content contains /spend 1h' do it 'populates spend_time: 3600 if content contains /spend 1h' do
_, updates = service.execute(content, issuable) _, updates = service.execute(content, issuable)
expect(updates).to eq(spend_time: { duration: 3600, user: developer }) expect(updates).to eq(spend_time: {
duration: 3600,
user: developer,
spent_at: DateTime.now.to_date
})
end end
end end
...@@ -217,7 +221,39 @@ describe QuickActions::InterpretService do ...@@ -217,7 +221,39 @@ describe QuickActions::InterpretService do
it 'populates spend_time: -1800 if content contains /spend -30m' do it 'populates spend_time: -1800 if content contains /spend -30m' do
_, updates = service.execute(content, issuable) _, updates = service.execute(content, issuable)
expect(updates).to eq(spend_time: { duration: -1800, user: developer }) expect(updates).to eq(spend_time: {
duration: -1800,
user: developer,
spent_at: DateTime.now.to_date
})
end
end
shared_examples 'spend command with valid date' do
it 'populates spend time: 1800 with date in date type format' do
_, updates = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 1800,
user: developer,
spent_at: Date.parse(date)
})
end
end
shared_examples 'spend command with invalid date' do
it 'will not create any note and timelog' do
_, updates = service.execute(content, issuable)
expect(updates).to eq({})
end
end
shared_examples 'spend command with future date' do
it 'will not create any note and timelog' do
_, updates = service.execute(content, issuable)
expect(updates).to eq({})
end end
end end
...@@ -711,6 +747,22 @@ describe QuickActions::InterpretService do ...@@ -711,6 +747,22 @@ describe QuickActions::InterpretService do
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'spend command with valid date' do
let(:date) { '2016-02-02' }
let(:content) { "/spend 30m #{date}" }
let(:issuable) { issue }
end
it_behaves_like 'spend command with invalid date' do
let(:content) { '/spend 30m 17-99-99' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with future date' do
let(:content) { '/spend 30m 6017-10-10' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do it_behaves_like 'empty command' do
let(:content) { '/spend' } let(:content) { '/spend' }
let(:issuable) { issue } let(:issuable) { issue }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment