def _loadActiviy(self): toggl = Toggl() toggl.setAPIKey(self._token) self._report_date = self._daily_date - timedelta(1) self.friday_text = '' if self._report_date.isoweekday() is 6 or self._report_date.isoweekday() is 7: self._report_date = self._daily_date - timedelta(3 if self._daily_date.isoweekday() is 1 else 2) self.friday_text = " (Friday)" self.report_day = '{0:%Y-%m-%d}'.format(self._report_date) me_info = toggl.request(toggl_urls.me) self._username = me_info['data']['fullname'] summary_url = toggl_urls.summary + "?workspace_id={}&since={}&until={}&user_agent=api_test".format( self._workspace_id, self.report_day, self.report_day) response = toggl.request(summary_url) activity = {} default_project = conf_manager.get_default_project() for data in response['data']: project_name = default_project if None == data['title']['project'] else data['title']['project'] activity[project_name] = [] for item in data['items']: hours = self.millis_to_hours(item['time']) self._total_hours += hours activity[project_name].append(Task(item['title']['time_entry'], project_name, hours).serialize()) self._activity = activity return activity
def toggl_get_tasks(token=None) -> List[TglTask]: toggl = Toggl() if token is None: token = os.getenv("TOGGL_API_TOKEN") toggl.setAPIKey(token) workspaces = [] for e in _retry(lambda: toggl.getWorkspaces()): workspaces.append((e["id"], e["name"])) projects = [] for wid, wname in workspaces: for e in _retry(lambda: toggl.request(Endpoints.WORKSPACES + f"/{wid}/projects")) or []: projects.append((e["id"], e["name"])) ptasks = [] for pid, pname in projects: for e in _retry(lambda: toggl.getProjectTasks(pid)) or [{ "id": 0, "name": "" }]: ptasks.append(TglTask(pid, pname, e["id"], e["name"])) return ptasks
class Toggls(): def __init__(self, key): self.toggl = Toggl() self.toggl.setAPIKey(key) def get_Workspaces(self): return self.toggl.getWorkspaces() def getprojectsinworkspace(self, id): urls = "https://www.toggl.com/api/v8/workspaces/" + str(id) + "/projects" return self.toggl.request(urls) def get_workspace(self, name): return self.toggl.getWorkspace(name) def make_time_entry(self, pid, workout): start = workout[1][6:10] + "-" + workout[1][:2] + "-" + workout[1][3:5] + "T" + workout[1][12:] + ".000Z" time = float(workout[2][0])*60*60 + float(workout[2][2:4])*60 + float(workout[2][5:7]) url = 'https://www.toggl.com/api/v8/time_entries' data = { "time_entry": { "description":"Workout", "tags":[""], "duration": time, "start": start, "pid": pid, "created_with":"api" } } return self.toggl.postRequest(url, parameters=data)
def get_data(self): toggl = Toggl() toggl.setAPIKey(self.config['api']) data = { 'workspace_id': workspace_id, 'user_agent': user_agent, 'since': self.last_month_first, 'until': self.last_month_last } self.response = toggl.request( "https://toggl.com/reports/api/v2/details", parameters=data) self.save_raw_data()
def toggl_get_entries_raw(date: datetime.date, token=None): toggl = Toggl() if token is None: token = os.getenv("TOGGL_API_TOKEN") toggl.setAPIKey(token) start_date = datetime.datetime.combine(date, datetime.time()).astimezone() params = {"start_date": start_date.isoformat()} params["end_date"] = (start_date + datetime.timedelta(days=1) + datetime.timedelta(minutes=-1)).isoformat() print(date) time.sleep(2) togglentries = toggl.request(Endpoints.TIME_ENTRIES, params) return togglentries
class TogglPyTests(unittest.TestCase): def setUp(self): self.api_key = os.environ["TOGGL_API_KEY"] if self.api_key is None: raise Exception("Unable to execute api tests without an api key") self.workspace_id = os.environ["WORKSPACE_ID"] if self.workspace_id is None: raise Exception( "Unable to execute api tests without a workspace key to query" ) self.toggl = Toggl() self.toggl.setAPIKey(self.api_key) def test_connect(self): response = self.toggl.request("https://api.track.toggl.com/api/v8/clients")
def get_entries(start, end, from_workspace_id, from_user_id): toggl = Toggl() toggl.setAPIKey(os.getenv('TOGGL_APIKEY')) response = toggl.request( f'https://toggl.com/reports/api/v2/details?workspace_id={from_workspace_id}&since={start}&until={end}&user_agent={user_agent}&uid={from_user_id}&include_time_entry_ids=true&user_ids={from_user_id}' ) if response['total_count'] > response['per_page']: raise Exception('Paging not supported, results exceed the page') for k, v in response.items(): if k != 'data': print(k, v) entries = [] for time_entry in response['data']: print(time_entry) entries.append(time_entry) return entries
#!/usr/bin/env python # # LaunchBar Action Script # from toggl.TogglPy import Toggl from requests import HTTPError # import os # %% toggl = Toggl() # fill your own E-Mail and Password. toggl.setAuthCredentials('<EMAIL>', '<PASSWORD>') # %% my_entries = toggl.request('https://www.toggl.com/api/v8/time_entries') for time_entry in my_entries[::-1]: description = time_entry.get('description', None) if description != 'Pomodoro Break': last_entry = time_entry break else: continue # %% # chech if now have tasks running. if last_entry['duration'] < 0: # notifi_title = 'Having Running' notifi_content = '⚠Now Have Running Task: ' + last_entry.get( 'description', 'No Description') else:
index_midnight = index.replace(hour=0, minute=1) index_formatted = index_midnight.isoformat() + 'Z' index_str = dt.strftime(index, '%Y-%m-%d') day_after_index = index + datetime.timedelta(1) day_after_index_midnight = day_after_index.replace(hour=0, minute=1) day_after_index_formatted = day_after_index_midnight.isoformat() + 'Z' #################################################### # Check if entries already exist in Toggl for date # #################################################### start_date_encoded = urllib.parse.quote(index_formatted) end_date_encoded = urllib.parse.quote(day_after_index_formatted) entries_on_day = toggl.request("https://www.toggl.com/api/v8/time_entries" + "?start_date=" + start_date_encoded + "&end_date=" + end_date_encoded) assert entries_on_day == [] ############## # Setup gcal # ############## gcal.init(index_formatted, day_after_index_formatted) from gcal import event_list print(event_list) #Function definitions def strip_and_datetime(time_string): stripped = str(time_string)[:19]
class TogglTimesheets: def __init__(self, api_key): # create a Toggl object and set our API key self.toggl = Toggl() self.toggl.setAPIKey(api_key) def _get_raw_timelogs(self, start_date=None, end_date=None): # Add filters params = {} if start_date: params["start_date"] = start_date if end_date: params["end_date"] = end_date # Make request and return return self.toggl.request(Endpoints.TIME_ENTRIES, parameters=params) def _get_raw_timelogs_last_n_days(self, n_days): last_n_days = datetime.utcnow() - timedelta(days=n_days) last_n_days_str = last_n_days.replace(microsecond=0, tzinfo=timezone.utc).isoformat() return self._get_raw_timelogs(start_date=last_n_days_str) @staticmethod def _parse_timelog(raw): """ { 'start': '2019-05-11T12:19:39+00:00', 'stop': '2019-05-11T12:40:27+00:00', 'description': 'Trying to deploy mongodb', 'tags': ['BB-1212'], 'duronly': False, } """ if raw.get('duronly', False): raise ("Error, timelog with duronly = true") tags = raw.get('tags', []) ticket = None dd = False ff = False for tag in tags: if ISSUE_REGEX.match(tag): ticket = tag elif tag == 'DD': dd = True elif tag == 'FF': ff = True if not ticket and 'description' in raw: possible = ISSUE_REGEX.findall(raw['description']) if possible: # TODO Dividing to multiple tickets ticket = possible[0] raw['description'] = raw['description'].strip(ticket).strip() if 'description' not in raw: # Shouldn't exist, but alas raw['description'] = 'NO DESCRIPTION' start = datetime.strptime(raw['start'], DATETIME_FORMAT) end = datetime.strptime(raw['stop'], DATETIME_FORMAT) return Timelog(ticket=ticket, date=start, time=end - start, description=raw['description'], ff_time=ff, dd_time=dd) def get_timelogs_last_n_days(self, n_days=1): raw_logs = self._get_raw_timelogs_last_n_days(n_days) result = { 'complete': list(), 'incomplete': list(), } for item in raw_logs: if 'stop' not in item: # Ignore currently running continue timelog = self._parse_timelog(item) if timelog.ticket: result['complete'].append(timelog) else: result['incomplete'].append(timelog) return result def get_timelogs(self, start_date, end_date): start_date_str = start_date.replace(microsecond=0, tzinfo=timezone.utc).isoformat() end_date_str = end_date.replace(microsecond=0, tzinfo=timezone.utc).isoformat() raw_logs = self._get_raw_timelogs(start_date=start_date_str, end_date=end_date_str) result = { 'complete': list(), 'incomplete': list(), } for item in raw_logs: if 'stop' not in item: # Ignore currently running continue timelog = self._parse_timelog(item) if timelog.ticket: result['complete'].append(timelog) else: result['incomplete'].append(timelog) return result
class TogglPyTests(unittest.TestCase): def setUp(self): self.api_key = os.environ['TOGGL_API_KEY'] if self.api_key is None: raise Exception("Unable to execute api tests without an api key") self.workspace_id = os.environ['WORKSPACE_ID'] if self.workspace_id is None: raise Exception("Unable to execute api tests without a workspace key to query") self.toggl = Toggl() self.toggl.setAPIKey(self.api_key) def test_connect(self): response = self.toggl.request("https://www.toggl.com/api/v8/clients") self.assertTrue(response is not None) def test_putTimeEntry(self): request_args = { 'workspace_id': self.workspace_id, } entries = self.toggl.getDetailedReport(request_args) #for this tests I'm tagging my Pomodoro Entries missing_projects = [r for r in entries['data'] if r['project'] is None and 'Pomodoro' in r['description'] ] me = missing_projects[0] me_id = me['id'] #remember for later #I've tagged my pomodoro entries as Self/Self cp = self.toggl.getClientProject("Self", "Self") project_id = cp['data']['id'] me['pid'] = project_id #his is the new stuff response = self.toggl.putTimeEntry({"id": me_id, "pid":project_id}) self.assertTrue(response is not None) self.assertTrue('data' in response) self.assertTrue(response['data']['pid'] == project_id) def test_getDetailedReportCSV(self): data = { 'workspace_id': self.workspace_id, } csvfile = 'data.csv' self.toggl.getDetailedReportCSV(data, csvfile) self.assertTrue(os.path.isfile(csvfile)) os.remove(csvfile) data = self.toggl.getDetailedReportCSV(data) self.assertTrue(data is not None) def test_getDetailedReport(self): data = { 'workspace_id': self.workspace_id, } d = self.toggl.getDetailedReport(data) self.assertTrue(d is not None) self.assertTrue(len(d.keys()) > 0 ) fields = ['total_count', 'total_currencies', 'total_billable', 'data'] for f in fields: self.assertTrue(f in d.keys()) data = d['data'] self.assertTrue(len(data)>0) dr = data[0] self.assertTrue('client' in dr) self.assertTrue('start' in dr) self.assertTrue('end' in dr) self.assertTrue('task' in dr) self.assertTrue('user' in dr) self.assertTrue('project' in dr)
#get total amount of productivity in last 7 days from toggl.TogglPy import Toggl toggl = Toggl() toggl.setAPIKey('53a2d3c3856e1ef39982074936cd0c51') response = toggl.request("https://toggl.com/reports/api/v2/weekly") print(response)
class TogglTimesheets: def __init__(self, api_key): # create a Toggl object and set our API key self.toggl = Toggl() self.toggl.setAPIKey(api_key) def _get_raw_timelogs(self, start_date=None, end_date=None): # Add filters params = {} if start_date: params["start_date"] = start_date if end_date: params["start_date"] = end_date # Make request and return return self.toggl.request(Endpoints.TIME_ENTRIES, parameters=params) def _get_raw_timelogs_last_n_days(self, n_days): last_n_days = datetime.utcnow() - timedelta(days=n_days) last_n_days_str = last_n_days.replace(microsecond=0, tzinfo=timezone.utc).isoformat() return self._get_raw_timelogs(start_date=last_n_days_str) @staticmethod def _parse_timelog(raw): """ { 'start': '2019-05-11T12:19:39+00:00', 'stop': '2019-05-11T12:40:27+00:00', 'description': 'Trying to deploy mongodb', 'tags': ['BB-1212'], 'duronly': False, } """ if raw.get('duronly', False): print(raw) raise ("Error, timelog with duronly = true") tags = raw['tags'] ticket = None dd = False ff = False for tag in tags: if re.match(ISSUE_REGEX, tag): ticket = tag elif tag == 'DD': dd = True elif tag == 'FF': ff = True start = datetime.strptime(raw['start'], DATETIME_FORMAT) end = datetime.strptime(raw['stop'], DATETIME_FORMAT) return TIMELOG(ticket=ticket, date=start, time=end - start, description=raw['description'], ff_time=ff, dd_time=dd) def get_timelogs_last_n_days(self, n_days=1): raw_logs = self._get_raw_timelogs_last_n_days(n_days) result = { 'complete': list(), 'incomplete': list(), } for item in raw_logs: timelog = self._parse_timelog(item) if timelog.ticket: result['complete'].append(timelog) else: result['incomplete'].append(timelog) return result
class TogglTimesheets: def __init__(self, api_key): # create a Toggl object and set our API key self.toggl = Toggl() self.toggl.setAPIKey(api_key) def _get_raw_timelogs(self, start_date=None, end_date=None): # Add filters params = {} if start_date: params["start_date"] = start_date if end_date: params["end_date"] = end_date entries = self.toggl.request(Endpoints.TIME_ENTRIES, parameters=params) work_project_id = 162120186 return [entry for entry in entries if entry["pid"] == work_project_id] def _get_raw_timelogs_last_n_days(self, n_days): last_n_days = datetime.utcnow() - timedelta(days=n_days) last_n_days_str = last_n_days.replace(microsecond=0, tzinfo=timezone.utc).isoformat() return self._get_raw_timelogs(start_date=last_n_days_str) @staticmethod def _parse_timelog(raw): """ { 'start': '2019-05-11T12:19:39+00:00', 'stop': '2019-05-11T12:40:27+00:00', 'description': 'Trying to deploy mongodb', 'tags': ['BB-1212'], 'duronly': False, } """ if raw.get("duronly", False): raise ("Error, timelog with duronly = true") tags = raw.get("tags", []) ticket = None dd = False ff = False for tag in tags: if ISSUE_REGEX.match(tag): ticket = tag elif tag == "DD": dd = True elif tag == "FF": ff = True if not ticket and "description" in raw: possible = ISSUE_REGEX.findall(raw["description"]) if possible: # TODO Dividing to multiple tickets ticket = possible[0] desc = re.sub(r'\[?%s\]?' % ticket, '', raw["description"]) raw["description"] = desc.strip() if "description" not in raw: # Shouldn't exist, but alas raw["description"] = "NO DESCRIPTION" start = datetime.strptime(raw["start"], DATETIME_FORMAT) end = datetime.strptime(raw["stop"], DATETIME_FORMAT) return Timelog( ticket=ticket, date=start, time=end - start, description=raw["description"], ff_time=ff, dd_time=dd, ) def get_timelogs_last_n_days(self, n_days=1): raw_logs = self._get_raw_timelogs_last_n_days(n_days) result = { "complete": list(), "incomplete": list(), } for item in raw_logs: if "stop" not in item: # Ignore currently running continue timelog = self._parse_timelog(item) if timelog.ticket: result["complete"].append(timelog) else: result["incomplete"].append(timelog) return result def get_timelogs(self, start_date, end_date): start_date_str = start_date.replace(microsecond=0, tzinfo=timezone.utc).isoformat() end_date_str = end_date.replace(microsecond=0, tzinfo=timezone.utc).isoformat() raw_logs = self._get_raw_timelogs(start_date=start_date_str, end_date=end_date_str) result = { "complete": list(), "incomplete": list(), } for item in raw_logs: if "stop" not in item: # Ignore currently running continue timelog = self._parse_timelog(item) if timelog.ticket: result["complete"].append(timelog) else: result["incomplete"].append(timelog) return result
class TogglReportingApp(tk.Tk): def __init__(self, *args, **kwargs): self.toggl_setup() self.tkinter_setup(*args, **kwargs) def toggl_setup(self): self.connect_to_toggl() self.get_toggl_project_data() self.get_toggl_report_data() self.define_preset_date_bounds() self.group_by = 'Project' self.description_groupings = [] self.date_bounds = {} def tkinter_setup(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) tk.Tk.wm_title(self, "Toggl Reporting") container = ttk.Frame(self) container.pack(side="top", fill="both", expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.frames = {} frame = StartPage(container, self) self.frames[StartPage] = frame frame.grid(row=0, column=0, sticky='nsew') """ for F in (StartPage): frame = F(container, self) self.frames[F] = frame frame.grid(row=0, column=0, sticky="nsew") """ self.show_frame(StartPage) def show_frame(self, cont): frame = self.frames[cont] frame.tkraise() # Establish the connection to the TogglAPI, and collect the project data. def connect_to_toggl(self): self.toggl = Toggl() self.toggl.setAPIKey(config.API_KEY) # Get information about the user's projects from Toggl def get_toggl_project_data(self): self.user_data = self.toggl.request( "https://www.toggl.com/api/v8/me?with_related_data=true") project_data = self.remove_empty_projects( self.user_data['data']['projects']) project_data_dict = { project_data[i]['name']: project_data[i] for i in range(0, len(project_data)) } self.master_project_list = project_data_dict # Unchanging "master" list client_data = self.user_data['data']['clients'] client_data_dict = { client_data[i]['name']: client_data[i] for i in range(0, len(client_data)) } self.master_client_list = client_data_dict # Unchanging "master" list self.client_list = client_data_dict # List of active projects to be displayed in the graph # Assigning clients to projects. for client in client_data: client_id = client['id'] client_name = client['name'] for project_name in self.master_project_list: if not 'cid' in self.master_project_list[project_name]: self.master_project_list[project_name]['client'] = False elif self.master_project_list[project_name][ 'cid'] == client_id: self.master_project_list[project_name][ 'client'] = client_name #self.project_list = list(self.master_project_list.keys()) self.project_list = [] # Return a version of the project list, where all projects without any tracked hours are removed. # (This also removes projects which have been deleted via Toggl, but are still retrieved via the API) def remove_empty_projects(self, project_list): to_delete = [] for i in range(0, len(project_list)): if not 'actual_hours' in project_list[i]: to_delete.append(i) for i in sorted(to_delete, reverse=True): del project_list[i] return project_list def get_toggl_report_data(self): years = 1 date_bounds = { 'start': datetime.now() - timedelta(days=365 * years), #Get reports from the last n years. 'end': datetime.now() } split_bounds = self.split_date_bounds(date_bounds) # A list of all the reports we gather from Toggl. (Max 1 year each) reports = [] for bounds in split_bounds: params = {'start': bounds['start'], 'end': bounds['end']} reports.append(self.get_report(params)) self.full_toggl_report = self.join_reports(reports) def define_preset_date_bounds(self): year = 365 month = 30 self.preset_date_bounds = { 'Past Week': 7, 'Past Month': month, 'Past 6 Months': month * 6, 'Past Year': year, 'Past 2 Years': year * 2, 'Past 5 Years': year * 5, 'Custom': 0 } def set_group_by(self, group_by): self.group_by = group_by # Make a request to Toggl to get a report of all data from a given time frame. def get_report(self, params): data = { 'workspace_id': config.WORKSPACE_ID, 'since': params['start'], 'until': params['end'], } return self.toggl.getDetailedReportCSV(data) def is_included_project(self, project): if project in self.project_list: return True else: return False def is_included_client(self, client): if client in self.client_list: return True else: return False def main_sequence(self): self.update_project_data() self.update_client_data() self.update_description_restrictions() report = self.full_toggl_report day = self.get_day() day = self.populate_day(day, report) self.create_graph(day) def update_project_data(self): user_selection = self.project_list self.project_list = {} for project_name in user_selection: self.project_list[project_name] = self.master_project_list[ project_name] def update_client_data(self): user_selection = self.client_list self.client_list = {} for client_name in user_selection: self.client_list[client_name] = self.master_client_list[ client_name] # Return a list containing a series of bounds, each of at most 1 year long. def split_date_bounds(self, bounds): span = self.get_report_span(bounds['start'], bounds['end']) number_of_years = math.ceil(span / 365) bounds = [] for year in range(number_of_years): remaining_days_after_subtraction = span - 365 start = datetime.now() - timedelta(days=span) if (remaining_days_after_subtraction > 0): span = remaining_days_after_subtraction end = datetime.now() - timedelta( days=span + 1 ) #(Plus 1 so we don't get an overlap at the edge of the bounds) else: end = datetime.now() bounds.append({'start': start, 'end': end}) bounds.reverse( ) # We reverse the order, because we need reverse-chronological in order to signal when to stop the populate_day function. return bounds # Return the length of time that a report covers. (In days) def get_report_span(self, start, end): span = end - start return span.days # Join the given reports together, saving them as a temporary csv file. def join_reports(self, reports_list): temporary_csv_file = tempfile.NamedTemporaryFile() for report in reports_list: with open(temporary_csv_file.name, 'ab') as csv: csv.write(report) return temporary_csv_file def update_description_restrictions(self): self.allowed_descriptions = [] self.excluded_descriptions = [] for i in self.description_search_list: description_row = self.description_search_list[i] value = description_row['entry'].get().lower() excluded = bool(description_row['exclude_checkbox_value'].get()) if excluded: self.excluded_descriptions.append(value) else: self.allowed_descriptions.append(value) # If there are no specifically allowed descriptions, say that an empty string is allowed. (Otherwise we'll return nothing). if not self.allowed_descriptions: self.allowed_descriptions = [''] # Return an dictionary containing projects with minutes set to zero. def get_day(self): minutesInDay = ( 60 * 24 ) - 1 #Subtract 1 because it is zero indexed. Minute 1440 is the following midnight, which will be empty. day = {} emptyDay = {} for i in range(0, minutesInDay + 1): emptyDay[i] = 0 if self.group_by == 'Project': for project in self.project_list: day[project] = emptyDay.copy() if self.group_by == 'Description': for grouping in self.description_groupings: day[grouping['title']] = emptyDay.copy() if self.group_by == 'Timeframe': for timeframe in self.date_bounds.values(): day[timeframe['name']] = emptyDay.copy() if self.group_by == 'None': day = emptyDay return day # Return the earliest date from a set of timeframes. def find_earliest_date_bound(self, timeframes): earliest = datetime.today() for date_bounds_pair in self.date_bounds.values(): start = date_bounds_pair['start'] if start < earliest: earliest = start return earliest # Populate the day dictionary with data from the report. def populate_day(self, day, report): earliest_date_bound = self.find_earliest_date_bound(self.date_bounds) with open(report.name, 'r') as file: reader = csv.DictReader(file) for row in reader: print(row) try: entry_date = datetime.strptime(row['Start date'], '%Y-%m-%d') except ValueError: continue if entry_date < earliest_date_bound: # If the entry is earlier than our earliest date bound, we stop. break print(row) project = row['Project'] description = row['Description'] client = row['Client'] # Skipping header rows from merged csv. if row['Email'] == 'Email': continue if not self.is_included_project(project): continue if not self.is_included_client(client): continue matched_timeframes = [] # Skipping entries which are outside of our date bounds. within_bounds = False for date_bounds_pair in self.date_bounds.values(): if date_bounds_pair[ 'start'] <= entry_date <= date_bounds_pair['end']: within_bounds = True if self.group_by == 'Timeframe': timeframe_name = date_bounds_pair['name'] if not timeframe_name in matched_timeframes: matched_timeframes.append(timeframe_name) if not within_bounds: continue description_match = False if self.group_by == 'Description': matched_description_groups = [] for description_grouping in self.description_groupings: for user_description in description_grouping[ 'descriptions']: if user_description in description.lower(): description_match = True grouping_title = description_grouping['title'] if not grouping_title in matched_description_groups: matched_description_groups.append( grouping_title) else: for allowed_description in self.allowed_descriptions: if allowed_description in description.lower(): description_match = True break for excluded_description in self.excluded_descriptions: if excluded_description in description.lower(): description_match = False if not description_match: continue startMinutes = self.getTimeInMinutes(row['Start time']) duration = self.getTimeInMinutes(row['Duration']) for i in range(startMinutes + 1, startMinutes + duration + 1): targetMinute = i if targetMinute >= 1440: targetMinute = abs(1440 - i) if self.group_by == 'Project': day[project][targetMinute] += 1 elif self.group_by == 'Description': for description_group in matched_description_groups: day[description_group][targetMinute] += 1 elif self.group_by == 'Timeframe': for timeframe in matched_timeframes: day[timeframe][targetMinute] += 1 elif self.group_by == 'None': day[targetMinute] += 1 day = self.remove_empty_categories(day) return day # Remove all the categories from a day which have zero minutes tracked over the time span. def remove_empty_categories(self, day): empty_categories = [] for category_name in day: if all(value == 0 for value in day[category_name].values()): empty_categories.append(category_name) for category_name in empty_categories: del day[category_name] return day def getTimeInMinutes(self, time): split = re.split(':', time) hours = int(split[0]) minutes = int(split[1]) minutes += 60 * hours return minutes def get_minutes_since_midnight(self): now = datetime.now() midnight = now.replace(hour=0, minute=0, second=0, microsecond=0) minutes = (now - midnight).seconds / 60 return minutes def create_graph(self, day): if self.group_by == 'Project': for project_name in day: hex_color = self.project_list[project_name]['hex_color'] plt.plot(list(day[project_name].keys()), list(day[project_name].values()), label=project_name, color=hex_color) elif self.group_by == 'Description': for grouping in self.description_groupings: title = grouping['title'] plt.plot(list(day[title].keys()), list(day[title].values()), label=title) elif self.group_by == 'Timeframe': for timeframe_name in day: plt.plot(list(day[timeframe_name].keys()), list(day[timeframe_name].values()), label=timeframe_name) elif self.group_by == 'None': plt.plot(list(day.keys()), list(day.values())) plt.ylabel('Frequency') positions = [] labels = [] for i in range(0, 24): positions.append(i * 60) time = str(i) + ':00' if len(time) < 5: time = '0' + time labels.append(time) if self.highlight_current_time.get(): minutes_since_midnight = self.get_minutes_since_midnight() plt.axvline(x=minutes_since_midnight) plt.xticks(positions, labels) plt.grid() plt.legend() mng = plt.get_current_fig_manager() mng.full_screen_toggle() plt.show()
def main(args): pp = pprint.PrettyPrinter(indent=4) # create a Toggl object and set our API key toggl_account = Toggl() toggl_account.setAPIKey(args.toggl_key) toggl_tz_str = toggl_account.request( "https://www.toggl.com/api/v8/me")['data']['timezone'] toggl_tz = pytz.timezone(toggl_tz_str) # figure out what ranges to sync for if args.days: edate = datetime.today().replace( hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) sdate = edate - timedelta(days=abs(args.days) + 1) elif args.daterange: dates = [dateparser.parse(x) for x in args.daterange] if len(dates) < 2: dates.append(datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)) dates = sorted(dates) sdate = dates[0] edate = dates[1] + timedelta(days=1) else: edate = datetime.today().replace( hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) sdate = edate + timedelta(days=-365) sdate_aw = toggl_tz.localize(sdate) edate_aw = toggl_tz.localize(edate) # do some fancy date windowing required for retrieving tasks from toggl toggl_dateranges = [] chunks = (edate - sdate).days // 180 partials = (edate - sdate).days % 180 for i in range((edate - sdate).days // 180): toggl_dateranges.append([ sdate + timedelta(days=i * 180), sdate + timedelta(days=(i + 1) * 180 - 1) ]) if partials: toggl_dateranges.append([ sdate + timedelta(days=chunks * 180), sdate + timedelta(days=chunks * 180 + partials) ]) toggl_dateranges = [[toggl_tz.localize(dr[0]), toggl_tz.localize(dr[1])] for dr in toggl_dateranges] # collect toggl entries toggl_entries = [] toggl_clients = toggl_account.getClients() toggl_workspaces = toggl_account.getWorkspaces() toggl_projects = [] for client in toggl_clients: toggl_projects = toggl_projects + toggl_account.getClientProjects( client['id']) for wid in [w['id'] for w in toggl_workspaces]: for dr in toggl_dateranges: entries_left = 1 page = 1 while entries_left > 0: entries = toggl_account.request( Endpoints.REPORT_DETAILED, { 'workspace_id': wid, 'user_agent': 'https://github.com/a3ng7n/timesheet-sync', 'page': page, 'since': dr[0], 'until': dr[1] }) toggl_entries = entries['data'] + toggl_entries entries_left = entries['total_count'] - entries[ 'per_page'] if page == 1 else entries_left - entries[ 'per_page'] page += 1 task_names = [{ 'id': str(x['pid']) + x['description'], 'pid': x['pid'], 'description': x['description'] } for x in toggl_entries] toggl_task_names = list({x['id']: x for x in task_names}.values()) toggl_task_names = sorted(toggl_task_names, key=lambda k: k['pid'] if k['pid'] else 0) for i, t in enumerate(toggl_task_names): t['id'] = i # collect harvest entries harvest_account = harvest.Harvest(uri=args.harvest_url, account_id=args.harvest_account_id, personal_token=args.harvest_key) try: harvest_user_id = [ x['user']['id'] for x in harvest_account.users if x['user']['email'] == args.harvest_email ].pop() except IndexError: print("Could not find user with email address: {0}".format( args.harvest_email)) raise tasks = [] harvest_entries = [] harvest_clients = harvest_account.clients() for client in harvest_clients: harvest_projects = harvest_account.projects_for_client( client['client']['id']) for project in harvest_projects: some_entries = harvest_account.timesheets_for_project( project['project']['id'], start_date=sdate_aw.isoformat(), end_date=edate_aw.isoformat()) for e in some_entries: e['day_entry']['client_id'] = [ y['project']['client_id'] for y in harvest_projects if y['project']['id'] == e['day_entry']['project_id'] ].pop() harvest_entries = harvest_entries + some_entries tasks = tasks + [{ **x['task_assignment'], 'client_id': client['client']['id'] } for x in harvest_account.get_all_tasks_from_project( project['project']['id'])] task_names = [{**x, 'id': str(x['client_id'])\ + str(x['project_id'])\ + str(x['task_id'])} for x in tasks] harvest_task_names = list({x['id']: x for x in task_names}.values()) harvest_task_names = sorted(harvest_task_names, key=lambda k: k['client_id']) for i, t in enumerate(harvest_task_names): t['id'] = i task_association = task_association_config(toggl_account, toggl_task_names, harvest_account, harvest_task_names) # organize toggl entries by dates worked delta = edate - sdate dates = [sdate + timedelta(days=i) for i in range(delta.days + 1)] combined_entries_dict = {} for date in dates: # collect entries from either platform on the given date from_toggl = [ x for x in toggl_entries if ((dateutil.parser.parse(x['start']).astimezone(toggl_tz) > toggl_tz.localize(date)) and ( dateutil.parser.parse(x['start']).astimezone(toggl_tz) <= toggl_tz.localize(date) + timedelta(days=1))) ] from_harvest = [ x['day_entry'] for x in harvest_entries if dateutil.parser.parse(x['day_entry']['spent_at']).astimezone( toggl_tz) == toggl_tz.localize(date) ] if from_toggl or from_harvest: combined_entries_dict[date] = { 'toggl': { 'raw': from_toggl, 'tasks': {} }, 'harvest': { 'raw': from_harvest, 'tasks': {} } } # organize raw entries into unique tasks, and total time for that day for platform in combined_entries_dict[date].keys(): for entry in combined_entries_dict[date][platform]['raw']: if platform == 'toggl': if entry['pid'] not in combined_entries_dict[date][ platform]['tasks'].keys(): combined_entries_dict[date][platform]['tasks'][ entry['pid']] = {} try: combined_entries_dict[date][platform]['tasks'][ entry['pid']][entry[ 'description']] += entry['dur'] / 3600000 except KeyError: combined_entries_dict[date][platform]['tasks'][ entry['pid']][entry[ 'description']] = entry['dur'] / 3600000 else: try: combined_entries_dict[date][platform]['tasks'][ entry['notes']] += entry['hours'] except KeyError: combined_entries_dict[date][platform]['tasks'][ entry['notes']] = entry['hours'] # add data to harvest add_to_harvest = [] for date, entry in combined_entries_dict.items(): if entry['toggl']['tasks'] and not entry['harvest']['tasks']: for pid in entry['toggl']['tasks'].keys(): for task in entry['toggl']['tasks'][pid].keys(): for hidpair in list( zip( task_association[pid][task] ['harvest_project_id'], task_association[pid] [task]['harvest_task_id'])): add_to_harvest.append({ 'project_id': hidpair[0], 'task_id': hidpair[1], 'spent_at': date.date().isoformat(), 'hours': round(entry['toggl']['tasks'][pid][task], 2), 'notes': task }) print("The following Toggl entries will be added to Harvest:") pp.pprint(add_to_harvest) if input("""Add the entries noted above to harvest? (y/n)""").lower() in ( 'y', 'yes'): for entry in add_to_harvest: pp.pprint( harvest_account.add_for_user(user_id=harvest_user_id, data=entry)) else: print('aborted') exit(1) print('done!') exit(0)
def print_post(): write_log("Received: " + str(dict(request.args))) if "action" not in request.args or len( request.args["action"]) == 0 or request.args["action"] not in [ "start", "stop" ]: err_msg = "ERR: Action not provided or is not 'start' or 'stop'", write_log(err_msg) return err_msg, 200 # 400 if "toggl_api_token" not in request.args or len( request.args["toggl_api_token"]) == 0: err_msg = "ERR: Toggl API token not provided" write_log(err_msg) return err_msg, 200 # 400 action = request.args["action"] api_token = request.args["toggl_api_token"] toggl = Toggl() toggl.setAPIKey(api_token) if action == "stop": currentTimer = toggl.currentRunningTimeEntry() response = toggl.stopTimeEntry(currentTimer['data']['id']) write_log("Received from toggl:" + str(response)) return response, 200 if "desc" not in request.args or len(request.args["desc"]) == 0: err_msg = "ERR: Description not provided" write_log(err_msg) return err_msg, 200 # 400 if "project" not in request.args or len(request.args["project"]) == 0: err_msg = "ERR: Project not provided" write_log(err_msg) return err_msg, 200 # 400 desc = request.args["desc"] project_name = request.args["project"] write_log("Sending request for workspaces") workspaces = toggl.request("https://api.track.toggl.com/api/v8/workspaces") all_projects = [] for w in workspaces: workspace_id = w["id"] resp = toggl.request( f"https://api.track.toggl.com/api/v8/workspaces/{workspace_id}/projects" ) # Resp can be none if a workspace has no projects if resp: all_projects += resp all_project_names = [p["name"] for p in all_projects] if project_name not in all_project_names: err_msg = f"ERR: Project with name `{project_name}` not found." write_log(err_msg) return err_msg, 200 # 404 project_id = -1 for p in all_projects: if p["name"] == project_name: project_id = p["id"] if project_id == -1: err_msg = f"ERR: No project ID can be found for project with name `{project_name}`." write_log(err_msg) return err_msg, 200 # 404 write_log(f"Performing action {action} on entry.") response = toggl.startTimeEntry(desc, project_id) write_log("Received from toggl:" + str(response)) return response, 200
class TogglPyTests(unittest.TestCase): def setUp(self): self.api_key = os.environ['TOGGL_API_KEY'] if self.api_key is None: raise Exception("Unable to execute api tests without an api key") self.workspace_id = os.environ['WORKSPACE_ID'] if self.workspace_id is None: raise Exception( "Unable to execute api tests without a workspace key to query") self.toggl = Toggl() self.toggl.setAPIKey(self.api_key) def test_connect(self): response = self.toggl.request( "https://api.track.toggl.com/api/v8/clients") self.assertTrue(response is not None) def test_putTimeEntry(self): request_args = { 'workspace_id': self.workspace_id, } entries = self.toggl.getDetailedReport(request_args) #for this tests I'm tagging my Pomodoro Entries missing_projects = [ r for r in entries['data'] if r['project'] is None and 'Pomodoro' in r['description'] ] me = missing_projects[0] me_id = me['id'] #remember for later #I've tagged my pomodoro entries as Self/Self cp = self.toggl.getClientProject("Self", "Self") project_id = cp['data']['id'] me['pid'] = project_id #his is the new stuff response = self.toggl.putTimeEntry({"id": me_id, "pid": project_id}) self.assertTrue(response is not None) self.assertTrue('data' in response) self.assertTrue(response['data']['pid'] == project_id) def test_getDetailedReportCSV(self): data = { 'workspace_id': self.workspace_id, } csvfile = 'data.csv' self.toggl.getDetailedReportCSV(data, csvfile) self.assertTrue(os.path.isfile(csvfile)) os.remove(csvfile) data = self.toggl.getDetailedReportCSV(data) self.assertTrue(data is not None) def test_getDetailedReport(self): data = { 'workspace_id': self.workspace_id, } d = self.toggl.getDetailedReport(data) self.assertTrue(d is not None) self.assertTrue(len(d.keys()) > 0) fields = ['total_count', 'total_currencies', 'total_billable', 'data'] for f in fields: self.assertTrue(f in d.keys()) data = d['data'] self.assertTrue(len(data) > 0) dr = data[0] self.assertTrue('client' in dr) self.assertTrue('start' in dr) self.assertTrue('end' in dr) self.assertTrue('task' in dr) self.assertTrue('user' in dr) self.assertTrue('project' in dr)
toggl = Toggl() TogglConfig = config['Toggl'] SettingsConfig = config['Settings'] if TogglConfig['auth_token']: print('Using Toggl token for authentication') toggl.setAPIKey(TogglConfig['user_token']) elif TogglConfig['auth_email']: print('Using Toggl email and password for authentication') toggl.setAuthCredentials(TogglConfig['user_email'], TogglConfig['user_password']) else: raise Exception('Please configure your Toggl authentication.') # Timer working = False while True: currentEntry = toggl.request( 'https://www.toggl.com/api/v8/time_entries/current') if currentEntry["data"] == None and working == True: working = False print("Not Working") elif currentEntry["data"] != None and working == False: working = True print("Working") time.sleep(int(SettingsConfig['interval']))