Skip to content
Open
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
3 changes: 3 additions & 0 deletions docs/user-guide/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,9 @@ Example:

$ watson config backend.url http://localhost:4242
$ watson config backend.token 7e329263e329
or
$ watson config backend.repo git@github.com:user/repo.git

$ watson sync
Received 42 frames from the server
Pushed 23 frames to the server
Expand Down
19 changes: 16 additions & 3 deletions docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,21 @@ $ watson config -e

### Backend

At this time there is no official backend for Watson. We are working on it. But in a near future, you will be able to synchronize Watson with a public (or your private) repository via the [`sync`](./commands.md#sync) command. To configure your repository please set up the `[backend]` section.
You will be able to synchronize Watson with a public (or your private) repository via the [`sync`](./commands.md#sync) command.
To configure your repository please set up the `[backend]` section.
You have two options for synchronization:

- The [crick](https://github.com/TailorDev/crick) server
- A git repository

If using crick, set `backend.url` and `backend.token`.
If using a git repository, set `backend.repo`.


#### `backend.repo` (default: empty)

The remote URL of a git repository to clone.
Something like `git@github.com:user/repo.git`.

#### `backend.url` (default: empty)

Expand Down Expand Up @@ -225,8 +239,7 @@ A basic configuration file looks like the following:
# Watson configuration

[backend]
url = https://api.crick.fr
token = yourapitoken
repo = git@github.com:user/repo.git

[options]
stop_on_start = true
Expand Down
7 changes: 5 additions & 2 deletions watson/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1519,16 +1519,19 @@ def sync(watson):
\b
$ watson config backend.url http://localhost:4242
$ watson config backend.token 7e329263e329
or
$ watson config backend.repo git@github.com:user/repo.git
\b
$ watson sync
Received 42 frames from the server
Pushed 23 frames to the server
"""
last_pull = arrow.utcnow()
pulled = watson.pull()
click.echo("Received {} frames from the server".format(len(pulled)))
click.secho(f'Received {pulled} frames from the server.', fg='green')

pushed = watson.push(last_pull)
click.echo("Pushed {} frames to the server".format(len(pushed)))
click.secho(f'Pushed {pushed} frames to the server.', fg='green')

watson.last_sync = arrow.utcnow()
watson.save()
Expand Down
148 changes: 108 additions & 40 deletions watson/watson.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from configparser import Error as CFGParserError
import arrow
import click
import subprocess

from .config import ConfigParser
from .frames import Frames
Expand Down Expand Up @@ -54,6 +55,7 @@ def __init__(self, **kwargs):
self.frames_file = os.path.join(self._dir, 'frames')
self.state_file = os.path.join(self._dir, 'state')
self.last_sync_file = os.path.join(self._dir, 'last_sync')
self.sync_dir = os.path.join(self._dir, 'sync_repo')

if 'frames' in kwargs:
self.frames = kwargs['frames']
Expand Down Expand Up @@ -371,66 +373,132 @@ def _get_remote_projects(self):
return self._remote_projects['projects']

def pull(self):
import requests
dest, headers = self._get_request_info('frames')

try:
response = requests.get(
dest, params={'last_sync': self.last_sync}, headers=headers
)
assert response.status_code == 200
except requests.ConnectionError:
raise WatsonError("Unable to reach the server.")
except AssertionError:
raise WatsonError(
"An error occurred with the remote "
"server: {}".format(response.json())
)
repo = self.config.get('backend', 'repo')
if repo:
# clone git repository if necessary
if not os.path.isdir(self.sync_dir):
sync_dir = self.sync_dir
subprocess.run(['git', 'clone', repo, sync_dir], check=True)

frames = response.json() or ()
# git pull
subprocess.run(['git', 'pull'], cwd=self.sync_dir, check=True)
sync_file = os.path.join(self.sync_dir, 'frames')
try:
with open(sync_file, 'r') as f:
frames = json.load(f)
except FileNotFoundError:
frames = []
else:
import requests
dest, headers = self._get_request_info('frames')

try:
response = requests.get(
dest, params={'last_sync': self.last_sync}, headers=headers
)
assert response.status_code == 200
except requests.ConnectionError:
raise WatsonError("Unable to reach the server.")
except AssertionError:
raise WatsonError(
"An error occurred with the remote "
"server: {}".format(response.json())
)

frames = response.json() or ()

updated_frames = 0
for frame in frames:
frame_id = uuid.UUID(frame['id']).hex
self.frames[frame_id] = (
frame['project'],
frame['begin_at'],
frame['end_at'],
frame['tags']
)
try:
self.frames[frame_id]
except KeyError:
updated_frames += 1
self.frames[frame_id] = (
frame['project'],
frame['begin_at'],
frame['end_at'],
frame['tags']
)

return frames
return updated_frames

def push(self, last_pull):
import requests
dest, headers = self._get_request_info('frames/bulk')

frames = []

for frame in self.frames:
if last_pull > frame.updated_at > self.last_sync:
repo = self.config.get('backend', 'repo')
if repo:
for frame in self.frames:
frames.append({
'id': uuid.UUID(frame.id).urn,
'begin_at': str(frame.start.to('utc')),
'end_at': str(frame.stop.to('utc')),
'project': frame.project,
'tags': frame.tags
})
frames.sort(key=lambda frame: frame['begin_at'])

try:
response = requests.post(dest, json.dumps(frames), headers=headers)
assert response.status_code == 201
except requests.ConnectionError:
raise WatsonError("Unable to reach the server.")
except AssertionError:
raise WatsonError(
"An error occurred with the remote server (status: {}). "
"Response was:\n{}".format(
response.status_code,
response.text
# Get number of synced frames
sync_file = os.path.join(self.sync_dir, 'frames')
try:
with open(sync_file, 'r') as f:
n_frames = len(json.load(f))
except FileNotFoundError:
n_frames = 0

# Write frames to repo
with open(sync_file, 'w') as f:
json.dump(frames, f, indent=2)

# Check if anything has changed
try:
command = ['git', 'diff', '--quiet']
subprocess.run(command, cwd=self.sync_dir, check=True)
return 0
except subprocess.CalledProcessError:
pass

# git push
cwd = self.sync_dir
updated = len(frames) - n_frames
msg = f'Add {updated} frames ({datetime.datetime.now()})'
subprocess.run(['git', 'add', 'frames'], cwd=cwd, check=True)
subprocess.run(['git', 'commit', '-m', msg], cwd=cwd, check=True)
subprocess.run(['git', 'push'], cwd=cwd, check=True)

return updated

else:
import requests
dest, headers = self._get_request_info('frames/bulk')

for frame in self.frames:
if last_pull > frame.updated_at > self.last_sync:
frames.append({
'id': uuid.UUID(frame.id).urn,
'begin_at': str(frame.start.to('utc')),
'end_at': str(frame.stop.to('utc')),
'project': frame.project,
'tags': frame.tags
})

try:
body = json.dumps(frames)
response = requests.post(dest, body, headers=headers)
assert response.status_code == 201
except requests.ConnectionError:
raise WatsonError("Unable to reach the server.")
except AssertionError:
raise WatsonError(
"An error occurred with the remote server (status: {}). "
"Response was:\n{}".format(
response.status_code,
response.text
)
)
)

return frames
return len(frames)

def merge_report(self, frames_with_conflict):
conflict_file_frames = Frames(self._load_json_file(
Expand Down