Exemplo n.º 1
0
    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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
    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()
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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")
Exemplo n.º 7
0
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
Exemplo n.º 8
0
#!/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:
Exemplo n.º 9
0
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
Exemplo n.º 11
0
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)
Exemplo n.º 12
0
#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)
Exemplo n.º 13
0
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
Exemplo n.º 14
0
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
Exemplo n.º 15
0
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()
Exemplo n.º 16
0
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)
Exemplo n.º 17
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
Exemplo n.º 18
0
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)
Exemplo n.º 19
0
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']))