Skip to content

Commit c1cbe65

Browse files
committed
feat(toggl): convert to Justworks formatted data
1 parent e4ab095 commit c1cbe65

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

compiler_admin/services/toggl.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
# default output CSV columns for Harvest
1818
HARVEST_COLUMNS = ["Date", "Client", "Project", "Task", "Notes", "Hours", "First name", "Last name"]
19+
# default output CSV columns for Justworks
20+
JUSTWORKS_COLUMNS = ["First Name", "Last Name", "Work Email", "Start Date", "End Date", "Regular Hours"]
1921

2022

2123
@cache
@@ -121,6 +123,52 @@ def convert_to_harvest(
121123
files.write_csv(output_path, source, columns=output_cols)
122124

123125

126+
def convert_to_justworks(
127+
source_path: str | TextIO = sys.stdin,
128+
output_path: str | TextIO = sys.stdout,
129+
output_cols: list[str] = JUSTWORKS_COLUMNS,
130+
**kwargs,
131+
):
132+
"""Convert Toggl formatted entries in source_path to equivalent Justworks formatted entries.
133+
134+
Args:
135+
source_path: The path to a readable CSV file of Toggl time entries; or a readable buffer of the same.
136+
137+
output_path: The path to a CSV file where Harvest time entries will be written; or a writeable buffer for the same.
138+
139+
output_cols (list[str]): A list of column names for the output
140+
141+
Returns:
142+
None. Either prints the resulting CSV data or writes to output_path.
143+
"""
144+
source = _prepare_input(
145+
source_path=source_path,
146+
column_renames={
147+
"Email": "Work Email",
148+
"First name": "First Name",
149+
"Hours": "Regular Hours",
150+
"Last name": "Last Name",
151+
"Start date": "Start Date",
152+
},
153+
)
154+
155+
# aggregate hours per person per day
156+
cols = ["Work Email", "First Name", "Last Name", "Start Date"]
157+
people = source.sort_values(cols).groupby(cols, observed=False)
158+
people_agg = people.agg({"Regular Hours": "sum"})
159+
people_agg.reset_index(inplace=True)
160+
161+
# aggregate hours per person and rollup to the week (starting on Sunday)
162+
cols = ["Work Email", "First Name", "Last Name"]
163+
weekly_agg = people_agg.groupby(cols).resample("W", label="left", on="Start Date")
164+
weekly_agg = weekly_agg["Regular Hours"].sum().reset_index()
165+
166+
# calculate the week end date (the following Saturday)
167+
weekly_agg["End Date"] = weekly_agg["Start Date"] + pd.Timedelta(days=6)
168+
169+
files.write_csv(output_path, weekly_agg, columns=output_cols)
170+
171+
124172
def download_time_entries(
125173
start_date: datetime,
126174
end_date: datetime,

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ def harvest_file():
191191
return "notebooks/data/harvest-sample.csv"
192192

193193

194+
@pytest.fixture
195+
def justworks_file():
196+
return "notebooks/data/justworks-sample.csv"
197+
198+
194199
@pytest.fixture
195200
def toggl_file():
196201
return "notebooks/data/toggl-sample.csv"

tests/services/test_toggl.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
_prepare_input,
1616
_str_timedelta,
1717
convert_to_harvest,
18+
convert_to_justworks,
1819
download_time_entries,
1920
TOGGL_COLUMNS,
2021
HARVEST_COLUMNS,
22+
JUSTWORKS_COLUMNS,
2123
files,
2224
)
2325

@@ -200,6 +202,34 @@ def test_convert_to_harvest_sample(toggl_file, harvest_file, mock_google_user_in
200202
assert output_df["Client"].eq("Test Client 123").all()
201203

202204

205+
def test_convert_to_justworks_mocked(toggl_file, spy_files):
206+
convert_to_justworks(toggl_file)
207+
208+
spy_files.write_csv.assert_called_once()
209+
call_args = spy_files.write_csv.call_args
210+
assert sys.stdout in call_args[0]
211+
assert call_args.kwargs["columns"] == JUSTWORKS_COLUMNS
212+
213+
214+
def test_convert_to_justworks_sample(toggl_file, justworks_file):
215+
output = None
216+
217+
with StringIO() as output_data:
218+
convert_to_justworks(toggl_file, output_data)
219+
output = output_data.getvalue()
220+
221+
assert output
222+
assert isinstance(output, str)
223+
assert ",".join(JUSTWORKS_COLUMNS) in output
224+
225+
order = ["Start Date", "First Name", "Regular Hours"]
226+
sample_output_df = pd.read_csv(justworks_file).sort_values(order)
227+
output_df = pd.read_csv(StringIO(output)).sort_values(order)
228+
229+
assert set(output_df.columns.to_list()) <= set(sample_output_df.columns.to_list())
230+
assert output_df.shape == sample_output_df.shape
231+
232+
203233
@pytest.mark.usefixtures("mock_toggl_api_env", "mock_toggl_detailed_time_entries")
204234
def test_download_time_entries(toggl_file):
205235
dt = datetime.now()

0 commit comments

Comments
 (0)