Beispiel #1
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)
Beispiel #2
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
Beispiel #3
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
def toggl_delete_entries_for_ids(ids, token=None):
    toggl = Toggl()
    if token is None:
        token = os.getenv("TOGGL_API_TOKEN")
    toggl.setAPIKey(token)

    for id_ in ids:
        endpoint = Endpoints.TIME_ENTRIES + f"/{id_}"
        toggl_http_request(toggl, endpoint, "DELETE")
class Toggl2GSuiteTest(unittest.TestCase):
    def setUp(self):
        self.api_key = os.environ["TOGGL_API_KEY"]
        self.toggl = Toggl()
        self.toggl.setAPIKey(self.api_key)

    # see https://stackoverflow.com/questions/19153462/get-excel-style-column-names-from-column-number
    LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    @staticmethod
    def excel_style(row, col):
        """ Convert given row and column number to an Excel-style cell name. """
        result = []
        while col:
            col, rem = divmod(col - 1, 26)
            result[:0] = Toggl2GSuiteTest.LETTERS[rem]
        return "".join(result) + str(row)

    def test_toggl2gsuite(self):
        # have to do this year by year
        data = {
            "workspace_id": os.environ["WORKSPACE_ID"],
        }
        y = self.toggl.getDetailedReport(data)

        credentials = ServiceAccountCredentials.from_json_keyfile_name(
            os.environ["KEYFILE"], ["https://spreadsheets.google.com/feeds"]
        )

        client = gspread.authorize(credentials)
        sheet = client.open_by_url(os.environ["SHEET_URL"])
        worksheet = sheet.get_worksheet(0)

        wrote_header = False
        columns_to_write = [
            "user",
            "updated",
            "start",
            "end",
            "client",
            "project",
            "description",
            "is_billable",
            "billable",
        ]
        cell_row = 0
        for row_idx, rec in enumerate(y["data"]):
            if wrote_header == False:
                for col_idx, header in enumerate(columns_to_write):
                    worksheet.update_acell(
                        Toggl2GSuiteTest.excel_style(row_idx + 1, col_idx + 1), header
                    )
                wrote_header = True
            for col_idx, header in enumerate(columns_to_write):
                worksheet.update_acell(
                    Toggl2GSuiteTest.excel_style(row_idx + 2, col_idx + 1), rec[header]
                )
Beispiel #6
0
def _get_toggl_client(api_key):
    client = Toggl()
    client.setAPIKey(api_key)

    try:
        client.getWorkspaces()
    except HTTPError:
        raise click.BadParameter('Invalid api key or connection problem')
    return client
Beispiel #7
0
def main():

    # Read config file
    config = read_config_file()
    api_key = config.get('general', 'api_key').strip()

    api = Toggl()
    api.setAPIKey(api_key)
    projects = download_projects(api)
    dump_projects_as_csv(projects, sys.stdout)
Beispiel #8
0
def toggl_add_entries(entries: List[TglStandardEntry],
                      tasks: List[TglTask],
                      token=None):
    toggl = Toggl()
    if token is None:
        token = os.getenv("TOGGL_API_TOKEN")
    toggl.setAPIKey(token)

    taskmap = {}
    for t in tasks:
        k = t.project_name, t.task_name
        v = t.project_id, t.task_id
        taskmap[k] = v

    togglentries = []
    for i, entry in enumerate(entries, start=1):
        try:
            try:
                pid, tid = taskmap[(entry.project, entry.task)]
            except KeyError:
                raise ConvertingError(
                    f"{entry.project}, {entry.task} not found")
            togglentry = {
                "description": entry.description,
                "pid": pid,
                "tid": tid if tid else None,
                "start": entry.start.astimezone().isoformat(),
                "duration": entry.duration.seconds,
                "created_with": "easytrack",
            }
            togglentries.append(togglentry)
        except ConvertingError as e:
            e.i = i
            e.line = e
            raise

    for i, togglentry in enumerate(togglentries, start=1):
        try:
            try:
                print(i, '/', len(togglentries))
                time.sleep(2)
                resp = toggl.postRequest(Endpoints.TIME_ENTRIES,
                                         {"time_entry": togglentry})
            except urllib.error.HTTPError as e:
                raise TogglError(body=e.fp.read().decode(), underlying=e)
            resp = toggl.decodeJSON(resp)
            if resp.get("data") is None:
                raise ConvertingError(f"data not found in resp {resp}")
        except ConvertingError as e:
            e.i = i
            e.line = e
            raise
Beispiel #9
0
 def main(self):
     API_TOKEN = certification.load_json()
     if not API_TOKEN:
         print('[WARN]:Please set the API token in config.json')
     toggl = Toggl()
     toggl.setAPIKey(API_TOKEN)
     args = argument.get_argument()
     if args.function.lower() == 'start':
         workspace_id = self.get_workspace_id(toggl)
         self.get_project(toggl, workspace_id)
         self.start(toggl)
     elif args.function.lower() == 'stop':
         self.stop(toggl)
Beispiel #10
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()
Beispiel #11
0
def team_weekly_report(destination, since, until, api_key, workspace):
    logger.info(
        "Downloading the team weekly report from {} until {} into {}".format(
            since, until, destination))
    toggl = Toggl()
    toggl.setAPIKey(api_key)

    data = {
        'workspace_id':
        workspace,  # see the next example for getting a workspace id
        'since': since,
        'until': until
    }
    try:
        result = toggl.getSummaryReport(data)
    except Exception as e:
        logger.error("Unable to download the team weekly data {}".format(e))
        return

    # Calculate hours and minutes
    total_ms = result['total_grand']
    if total_ms:
        hours, minutes = divmod(total_ms / 1000 / 60, 60)
        time_str = "Total team hours: {:.0f}h {:.0f}m".format(hours, minutes)
    else:
        time_str = "Total team hours: No hours recorded"

    # Find all project worked on
    items_worked_on = [
        item["title"]["time_entry"] for project in result["data"]
        for item in project["items"]
    ]
    if len(items_worked_on) == 0:
        items_worked_on = ["No tasks worked on for this time period"]

    # Calculate the pretty data for the start of the week
    date = datetime.strptime(since, "%Y-%m-%d")
    formatted_week = date.strftime("%B %d")
    formatted_items = "- " + "\n- ".join(items_worked_on)
    formatted_team_report = team_report_template.format(
        formatted_week, formatted_items, time_str)

    logger.info("Created team report:")
    logger.info(formatted_team_report)

    logger.info("Adding to team log file %s", destination)

    with open(destination, "a") as report:
        report.write(formatted_team_report)

    logger.info("Done team report")
Beispiel #12
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
Beispiel #13
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")
Beispiel #14
0
class Toggl2GSuiteTest(unittest.TestCase):
    def setUp(self):
        self.api_key = os.environ['TOGGL_API_KEY']
        self.toggl = Toggl()
        self.toggl.setAPIKey(self.api_key)

    # see https://stackoverflow.com/questions/19153462/get-excel-style-column-names-from-column-number
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    @staticmethod
    def excel_style(row, col):
        """ Convert given row and column number to an Excel-style cell name. """
        result = []
        while col:
            col, rem = divmod(col - 1, 26)
            result[:0] = Toggl2GSuiteTest.LETTERS[rem]
        return ''.join(result) + str(row)

    def test_toggl2gsuite(self):
        # have to do this year by year
        data = {
            'workspace_id': os.environ['WORKSPACE_ID'],
        }
        y = self.toggl.getDetailedReport(data)


        credentials = ServiceAccountCredentials.from_json_keyfile_name(
            os.environ['KEYFILE'],
            ['https://spreadsheets.google.com/feeds'])

        client = gspread.authorize(credentials)
        sheet = client.open_by_url(os.environ['SHEET_URL'])
        worksheet = sheet.get_worksheet(0)

        wrote_header = False
        columns_to_write = ['user', 'updated', 'start', 'end', 'client', 'project', 'description', 'is_billable',
                            'billable']
        cell_row = 0
        for row_idx, rec in enumerate(y['data']):
            if wrote_header == False:
                for col_idx, header in enumerate(columns_to_write):
                    worksheet.update_acell(Toggl2GSuiteTest.excel_style(row_idx + 1, col_idx + 1), header)
                wrote_header = True
            for col_idx, header in enumerate(columns_to_write):
                worksheet.update_acell(Toggl2GSuiteTest.excel_style(row_idx + 2, col_idx + 1), rec[header])
Beispiel #15
0
def user_weekly_report(directory, since, until, api_key, users, workspace):
    """
    Downloads each users weekly reports as a summary and details pdf into their own directories.
    :param workspace:
    :param api_key: The toggl api key to use.
    :param users: The dictionary are users with the key as the user id and the values as the full name.
    :param directory: The root destination directory. Each user will have a sub-directory created for their reports
    :param since: The start date in the form yyyy-MM-dd
    :param until: the end data in the form yyyy-MM-dd. Files will be prepended with this date string.
    :return: None
    """
    logging.info(
        "Downloading user weekly reports from {} until {} into {}".format(
            since, until, directory))
    toggl = Toggl()
    toggl.setAPIKey(api_key)

    for uid, name in users.items():
        logger.info("Downloading reports for {}".format(name))
        name = users[uid]
        data = {
            'workspace_id':
            workspace,  # see the next example for getting a workspace id
            'since': since,
            'until': until,
            'user_ids': uid
        }

        folder = path.join(directory, name)

        if not path.exists(folder):
            logger.info("Creating the folder {}".format(folder))
            makedirs(folder)

        details = path.join(folder, until + "-details.pdf")
        summary = path.join(folder, until + "-summary.pdf")

        try:
            toggl.getDetailedReportPDF(data, details)
            logger.info("Downloaded {}".format(details))
            toggl.getSummaryReportPDF(data, summary)
            logger.info("Downloaded {}".format(summary))
        except Exception as e:
            logging.error(e)
Beispiel #16
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
def get_projects():
    """Since the current project function in PyToggl doesn't work, I made my own
    hacky version. Gets all projects assigned to a client and returns it as a
    list"""

    toggl = Toggl()

    toggl.setAPIKey(TOGGL_TOKEN)

    clients = toggl.getClients()

    toggl_projects = []

    for client in clients:
        projects = toggl.getClientProjects(client["id"])
        if projects != None:
            for project in projects:
                toggl_projects.append(project)

    return toggl_projects
Beispiel #18
0
def save_entries(entries, workspace_id, user_id, project_id, hourdiff_to_utc):
    toggl = Toggl()

    toggl.setAPIKey(os.getenv('TOGGL_APIKEY'))
    for entry in entries:
        start = entry['start']
        start_date = datetime.datetime.strptime(start, '%Y-%m-%dT%H:%M:%S%z')
        y = start_date.year
        m = start_date.month
        d = start_date.day
        h = start_date.hour
        desc = entry['description']
        print(entry['dur'])
        toggl.createTimeEntry(round(entry['dur'] / 3600000.0, 1),
                              description=desc,
                              projectid=project_id,
                              year=y,
                              month=m,
                              day=d,
                              hour=h,
                              hourdiff=hourdiff_to_utc)
Beispiel #19
0
class TogglApi:
    def __init__(self):
        self.config = load_config()
        api_token = self.config['toggl']['token']
        self.toggl = Toggl()
        self.toggl.setAPIKey(api_token)

    def preset_projects(self):
        return self.config['toggl']['preset_projects']

    def current_timer(self):
        timer = self.toggl.currentRunningTimeEntry()['data']
        if timer:
            if 'pid' in timer:
                project = self.get_project_name(timer['pid'])
            else:
                project = ['Kein Projekt', (244, 244, 6)]

            timer_dic = {
                'name': timer.get('description', ""),
                'id': timer['id'],
                'start_time':
                datetime.fromtimestamp(int(timer['duration']) * -1),
                'project_name': project[0],
                'project_color': project[1]
            }
            # start = datetime.strptime(timer['start'],"%Y-%m-%dT%H:%M:%S+00:00")
            return timer_dic
        else:
            return {
                'name': "",
                'id': "1234",
                'start_time': datetime.fromtimestamp(1542385078),
                'project_name': "Kein Projekt",
                'project_color': (30, 0, 0)
            }

    def get_project_name(self, pid):
        project = self.toggl.getProject(pid)['data']
        name = project['name']
        try:
            color = self.hex_to_rgb(project['hex_color'])
            color = (int(color[0] / 2), int(color[1] / 2), int(color[2] / 2))
        except ():
            color = (200, 200, 200)
        return name, color

    def start_timer(self, project_name, description):
        print(self.config['toggl']['project_ids'][project_name])
        self.toggl.startTimeEntry(
            description, self.config['toggl']['project_ids'][project_name])

    def stop_timer(self):
        current_timer_id = self.current_timer()['id']
        if current_timer_id:
            self.toggl.stopTimeEntry(current_timer_id)
            return True
        return False

    @staticmethod
    def hex_to_rgb(hex_str):
        hex = hex_str[1:]  # remove pound sign
        # https://stackoverflow.com/questions/29643352/converting-hex-to-rgb-value-in-python
        return tuple(int(hex[i:i + 2], 16) for i in (0, 2, 4))
Beispiel #20
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)
Beispiel #21
0
# Integration imports
from credentials import TOGGL_TOKEN, AIRTABLE_API_KEY
from toggl.TogglPy import Toggl
from airtable import Airtable
from airtable_integrations import *
import gcal

#Utilities
import re
from pprint import pprint
from task_dicts import event_exclude
import urllib

toggl = Toggl()

toggl.setAPIKey(TOGGL_TOKEN)

offset = int(sys.argv[1])

#Generate today-string
index = dt.today() + datetime.timedelta(offset)
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 #
Beispiel #22
0
def getTogglClient(token: str) -> Toggl:
    toggl = Toggl()
    toggl.setAPIKey(token)
    return toggl
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
Beispiel #24
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)
Beispiel #25
0
def _get_toggl(user: User) -> Toggl:
    toggl = Toggl()
    toggl.setAPIKey(user.togglcredentials.api_key)
    return toggl
Beispiel #26
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)
Beispiel #27
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
Beispiel #28
0
def run():

    # Connect with Google Calendar

    from google.auth import app_engine
    import googleapiclient.discovery

    # Second method using OAuth 2.0
    from google.oauth2 import service_account

    project = "toggltocalendar"
    SCOPES = ['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events']

    credentials = service_account.Credentials.from_service_account_file(os.environ['GOOGLE_APPLICATION_CREDENTIALS'], scopes=SCOPES)

    # Explicitly use App Engine credentials. These credentials are
    # only available when running on App Engine Standard.
    # credentials = app_engine.Credentials(scopes=SCOPES)

    # Explicitly pass the credentials to the client library.
    # storage_client = googleapiclient.discovery.build('storage', 'v1', credentials=credentials)

    # Make an authenticated API request
    # buckets = storage_client.buckets().list(project=project).execute()
    # print(buckets)


    # Connect with Google Calendar to read and create events
    service = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials)

    calendars = service.calendarList().list().execute()

    print(calendars)

    # Create toggl object
    toggl = Toggl()

    toggl.setAPIKey('2e8cb0eb95b4d98c98564e4ba8ee26ee')

    duration = 1 # day(s)
    until = datetime.datetime.now()
    since = until - datetime.timedelta(days=duration)
    page = 1
    more_results = True

    # Run while we have more results on the next page
    while (more_results):
        data =  {
            'workspace_id': 2705063,
            'since': since.strftime("%Y-%m-%d"),
            'until': until.strftime("%Y-%m-%d"),
            'page': page
        }
        response = toggl.getDetailedReport(data)

        df = pd.DataFrame(response['data'])

        # For each new entry, create a new Calendar entry

        for i, row in df.iterrows:
            createNewCalendarEntry(i)

        page += 1
        total_results = response['total_count']

        more_results = ((page-1) * 50 < total_results)
Beispiel #29
0
class TogglDelegate(ZeiDelegate):
    def __init__(self, periph, config):
        self.config = config
        self.toggl = Toggl()
        self.toggl.setAPIKey(self.config["toggl"]["settings"]["token"])
        self._populateProjects()
        self._populateMappings(self.config["mappings"])
        super().__init__(periph)

    def handleNotification(self, cHandle, data):
        if cHandle == 38:  # Side Change Notification
            side = struct.unpack("B", data)[0]
            self._trackProjectByMapping(
                self.mappings[side] if side in self.mappings else self.mappings[0]
            )
        else:
            _log.info("Notification from hndl: %s - %r", cHandle, data)

    def _trackProjectByMapping(self, mapping: Mapping):
        self._trackProject(
            description=mapping.description, pid=mapping.id, tags=mapping.tags
        )

    def _trackProject(self, description: str, pid: int, tags: list):
        current = self.toggl.currentRunningTimeEntry()["data"]

        if current is not None:
            if (
                datetime.now(timezone.utc) - dateutil.parser.isoparse(current["start"])
            ).total_seconds() < 20:
                # Delete entry if not older than 20s
                _log.info("Abort currently running entry")
                self.toggl.deleteTimeEntry(current["id"])
            else:
                _log.info("Stopping currently running entry")
                self.toggl.stopTimeEntry(current["id"])

        if pid not in self.projects:
            _log.info("Project not found, aborting")
            return

        _log.info(
            "Now tracking project %s: %s (%s)",
            self.projects[pid]["name"],
            description,
            ", ".join(tags if tags else []),
        )

        notification.notify(
            title="Toggl",
            message=f"Now tracking project {self.projects[pid]['name']} {description} ({', '.join(tags) if tags else ''})",
            app_name="Toggl",
            app_icon=join(dirname(realpath(__file__)), "icons/toggl.png"),
            timeout=5,
        )
        if pid == 0:
            return

        self.toggl.startTimeEntry(description, pid=pid, tags=tags)

    def _populateMappings(self, mappings: dict):
        self.mappings = {0: Mapping(0, 0)}
        for i in mappings:
            self.mappings[int(i)] = Mapping(int(i), int(mappings[i]["id"]))
            if "description" in mappings[i]:
                self.mappings[int(i)].description = mappings[i]["description"]
            if "tags" in mappings[i]:
                self.mappings[int(i)].tags = mappings[i]["tags"]

    def _populateProjects(self):
        self.projects = {}
        proj = self.toggl.getWorkspaceProjects(
            self.config["toggl"]["settings"]["workspace_id"]
        )
        NoneProj = {
            "id": 0,
            "wid": int(self.config["toggl"]["settings"]["workspace_id"]),
            "name": "None",
            "billable": False,
            "is_private": True,
            "active": True,
            "template": False,
            "at": "2020-06-09T04:02:38+00:00",
            "created_at": "2019-12-09T16:36:28+00:00",
            "color": "9",
            "auto_estimates": False,
            "actual_hours": 0,
            "hex_color": "#990099",
        }

        self.projects[0] = NoneProj
        for i in proj:
            self.projects[i["id"]] = i
Beispiel #30
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()
Beispiel #31
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)
Beispiel #32
0
import os
import urllib
import datetime
from toggl.TogglPy import Toggl

from app import db
from app.models import Workspace, Client, Member, Project, Task

toggl = Toggl()
TOGGL_API_KEY = os.environ.get('TOGGL_API_KEY')
toggl.setAPIKey(TOGGL_API_KEY)

DEFAULT_LONG_TASK = 3600000  # 1 hour

WORKSPACE_ID = 3122278

USER_AGENT = 'api_test'
REPORTS_URL = 'https://toggl.com/reports/api/v2/'
WORKSPACES_URL = 'https://www.toggl.com/api/v8/workspaces/'
PROJECTS_URL = 'https://www.toggl.com/api/v8/projects/'


def toggl_api(duration=None):

    # Check WorkSpace
    workspace = Workspace.query.filter_by(id=WORKSPACE_ID).first()
    if workspace is None:
        workspace = Workspace(id=WORKSPACE_ID, name='Vitamin')
        db.session.add(workspace)
        db.session.commit()