Thanks to visit codestin.com
Credit goes to github.com

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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Log output order can now be controlled via the `--reverse/--no-reverse` flag
and the `reverse_log` configuration option (#369)
- Add `--at` flag to the `start` and `restart` commands (#364).

### Changed

Expand Down
12 changes: 12 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,15 @@ def test_stop_valid_time(runner, watson, mocker, at_dt):
arrow.arrow.datetime.now.return_value = (start_dt + timedelta(hours=1))
result = runner.invoke(cli.stop, ['--at', at_dt], obj=watson)
assert result.exit_code == 0


# watson start

@pytest.mark.parametrize('at_dt', VALID_TIMES_DATA)
def test_start_valid_time(runner, watson, mocker, at_dt):
# Simulate a start date so that 'at_dt' is older than now().
mocker.patch('arrow.arrow.datetime', wraps=datetime)
start_dt = datetime(2019, 4, 10, 14, 0, 0, tzinfo=tzlocal())
arrow.arrow.datetime.now.return_value = (start_dt + timedelta(hours=1))
result = runner.invoke(cli.start, ['a-project', '--at', at_dt], obj=watson)
assert result.exit_code == 0
30 changes: 26 additions & 4 deletions tests/test_watson.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,26 @@ def test_start_nogap(watson):
assert watson.frames[-1].stop == watson.current['start']


def test_start_project_at(watson):
now = arrow.now()
watson.start('foo', start_at=now)
watson.stop()

# Task can't start before the previous task ends
with pytest.raises(WatsonError):
time_str = '1970-01-01T00:00'
time_obj = arrow.get(time_str)
watson.start('foo', start_at=time_obj)

# Task can't start in the future
with pytest.raises(WatsonError):
time_str = '2999-12-31T23:59'
time_obj = arrow.get(time_str)
watson.start('foo', start_at=time_obj)

assert watson.frames[-1].start == now


# stop

def test_stop_started_project(watson):
Expand Down Expand Up @@ -311,13 +331,15 @@ def test_stop_started_project_at(watson):
watson.start('foo')
now = arrow.now()

# Task can't end before it starts
with pytest.raises(WatsonError):
time_str = '1970-01-01T00:00'
time_obj = arrow.get(time_str)
watson.stop(stop_at=time_obj)

with pytest.raises(ValueError):
time_str = '2999-31-12T23:59'
# Task can't end in the future
with pytest.raises(WatsonError):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was previously raisin ValueError because there is no month number 31. Now it correctly raises a WatsonError if the task starts in the future.

time_str = '2999-12-31T23:59'
time_obj = arrow.get(time_str)
watson.stop(stop_at=time_obj)

Expand Down Expand Up @@ -635,14 +657,14 @@ def json(self):
{
'id': '1c006c6e-6cc1-4c80-ab22-b51c857c0b06',
'project': 'foo',
'start_at': 4003,
'begin_at': 4003,
'end_at': 4004,
'tags': ['A']
},
{
'id': 'c44aa815-4d77-4a58-bddd-1afa95562141',
'project': 'bar',
'start_at': 4004,
'begin_at': 4004,
'end_at': 4005,
'tags': []
}
Expand Down
32 changes: 26 additions & 6 deletions watson/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,12 @@ def help(ctx, command):
click.echo(cmd.get_help(ctx))


def _start(watson, project, tags, restart=False, gap=True):
def _start(watson, project, tags, restart=False, start_at=None, gap=True):
"""
Start project with given list of tags and save status.
"""
current = watson.start(project, tags, restart=restart, gap=gap)
current = watson.start(project, tags, restart=restart, start_at=start_at,
gap=gap,)
click.echo(u"Starting project {}{} at {}".format(
style('project', project),
(" " if current['tags'] else "") + style('tags', current['tags']),
Expand All @@ -175,7 +176,12 @@ def _start(watson, project, tags, restart=False, gap=True):


@cli.command()
@click.option('--at', 'at_', type=DateTime, default=None,
cls=MutuallyExclusiveOption, mutually_exclusive=['gap_'],
help=('Start frame at this time. Must be in '
'(YYYY-MM-DDT)?HH:MM(:SS)? format.'))
@click.option('-g/-G', '--gap/--no-gap', 'gap_', is_flag=True, default=True,
cls=MutuallyExclusiveOption, mutually_exclusive=['at_'],
help=("(Don't) leave gap between end time of previous project "
"and start time of the current."))
@click.argument('args', nargs=-1,
Expand All @@ -187,7 +193,8 @@ def _start(watson, project, tags, restart=False, gap=True):
@click.pass_obj
@click.pass_context
@catch_watson_error
def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True):
def start(ctx, watson, confirm_new_project, confirm_new_tag, args, at_,
gap_=True):
"""
Start monitoring time for the given project.
You can add tags indicating more specifically what you are working on with
Expand All @@ -197,6 +204,16 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True):
`options.stop_on_start` is set to a true value (`1`, `on`, `true`, or
`yes`), it is stopped before the new project is started.

If `--at` option is given, the provided starting time is used. The
specified time must be after the end of the previous frame and must not be
in the future.

Example:

\b
$ watson start --at 13:37
Starting project apollo11 at 13:37

If the `--no-gap` flag is given, the start time of the new project is set
to the stop time of the most recently stopped project.

Expand Down Expand Up @@ -239,7 +256,7 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True):
watson.config.getboolean('options', 'stop_on_start')):
ctx.invoke(stop)

_start(watson, project, tags, gap=gap_)
_start(watson, project, tags, start_at=at_, gap=gap_)


@cli.command(context_settings={'ignore_unknown_options': True})
Expand Down Expand Up @@ -275,13 +292,16 @@ def stop(watson, at_):


@cli.command(context_settings={'ignore_unknown_options': True})
@click.option('--at', 'at_', type=DateTime, default=None,
help=('Start frame at this time. Must be in '
'(YYYY-MM-DDT)?HH:MM(:SS)? format.'))
@click.option('-s/-S', '--stop/--no-stop', 'stop_', default=None,
help="(Don't) Stop an already running project.")
@click.argument('frame', default='-1', autocompletion=get_frames)
@click.pass_obj
@click.pass_context
@catch_watson_error
def restart(ctx, watson, frame, stop_):
def restart(ctx, watson, frame, stop_, at_):
"""
Restart monitoring time for a previously stopped project.

Expand Down Expand Up @@ -329,7 +349,7 @@ def restart(ctx, watson, frame, stop_):

frame = get_frame_from_argument(watson, frame)

_start(watson, frame.project, frame.tags, restart=True)
_start(watson, frame.project, frame.tags, restart=True, start_at=at_)


@cli.command()
Expand Down
20 changes: 17 additions & 3 deletions watson/watson.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ def add(self, project, from_date, to_date, tags):
frame = self.frames.add(project, from_date, to_date, tags=tags)
return frame

def start(self, project, tags=None, restart=False, gap=True):
def start(self, project, tags=None, restart=False, start_at=None,
gap=True):
if self.is_started:
raise WatsonError(
u"Project {} is already started.".format(
Expand All @@ -263,7 +264,20 @@ def start(self, project, tags=None, restart=False, gap=True):
if not restart:
tags = (tags or []) + default_tags

if start_at is None:
start_at = arrow.now()
elif self.frames:
# Only perform this check if an explicit start time was given
# and previous frames exist
stop_of_prev_frame = self.frames[-1].stop
if start_at < stop_of_prev_frame:
raise WatsonError('Task cannot start before the previous task '
'ends.')
if start_at > arrow.now():
raise WatsonError('Task cannot start in the future.')

new_frame = {'project': project, 'tags': deduplicate(tags)}
new_frame['start'] = start_at
if not gap:
stop_of_prev_frame = self.frames[-1].stop
new_frame['start'] = stop_of_prev_frame
Expand Down Expand Up @@ -385,7 +399,7 @@ def pull(self):
frame_id = uuid.UUID(frame['id']).hex
self.frames[frame_id] = (
frame['project'],
frame['start_at'],
frame['begin_at'],
frame['end_at'],
frame['tags']
)
Expand All @@ -402,7 +416,7 @@ def push(self, last_pull):
if last_pull > frame.updated_at > self.last_sync:
frames.append({
'id': uuid.UUID(frame.id).urn,
'start_at': str(frame.start.to('utc')),
'begin_at': str(frame.start.to('utc')),
'end_at': str(frame.stop.to('utc')),
'project': frame.project,
'tags': frame.tags
Expand Down