Exemplo n.º 1
0
def fetch(settings: typing.Dict[str, typing.Any], since: datetime.date,
          until: datetime.date) -> Report:
    """Fetch Toggle report."""
    LOGGER.info(
        'Fetching Toggl summary report: since=%s, until=%s, workspace_id=%s',
        since, until, settings.get('workspace_id'))

    client = TogglClientApi(settings)

    params = settings
    params.pop('token')
    params['since'] = str(since)
    params['until'] = str(until)
    params['grouping'] = 'projects'
    params['subgrouping'] = 'time_entries'

    response = client.query_report('/summary', params=params)
    response_data = response.json()

    if response.status_code != 200:
        raise RuntimeError(
            'Cannot fetch Toggl report: {}'.format(response_data))

    LOGGER.debug('Response: %s', pprint.pformat(response_data))

    total_grand = response_data['total_grand']
    if not total_grand:
        raise RuntimeError('Toggl returned no data: {}'.format(response_data))

    hours = Decimal(total_grand) / Decimal(1_000 * 60 * 60)
    report = Report(hours=hours)
    LOGGER.info('Fetched report: %s', report)

    return report
 def test_overriding_default_base_url_and_version_on_instance_creation(self):
     my_base_url = 'http://myownapi.com'
     my_ver_no = 12
     credentials = {
         'base_url': my_base_url,
         'ver_api': my_ver_no
     }
     self.api = TogglClientApi(credentials)
     self.assertEqual(self.api.api_base_url, my_base_url + '/v' + str(my_ver_no))
    def setUp(self):
        self.settings = {
            'workspace_name': 'A Company',
            'token': 'xxx',
            'base_url': 'https://my.service/api',
            'ver_api': 8,
            'base_url_report': 'https://my.service/reports/api',
            'ver_report': 2
        }
        self.api = TogglClientApi(self.settings)
        httpretty.enable()  # enable HTTPretty so that it will monkey patch the socket module

        self.base_api_url = self.api.build_api_url(self.settings['base_url'], self.settings['ver_api'])
        self.base_api_report_url = self.api.build_api_url(self.settings['base_url_report'], self.settings['ver_report'])
    def __init__(self, look_back_hours: int = 8, event=None) -> None:
        self.look_back_hours = look_back_hours
        self.time_from: str = None  # Format 2021-02-13T08:27:13.772498Z
        self.time_to: str = None
        self.timezone: timezone = timezone("Europe/Budapest")

        # Load credentials
        try:
            with open('token.pickle', 'rb') as token:
                self.creds, self.toogle_settings = pickle.load(token)
                self.toggl_client = TogglClientApi(self.toogle_settings)
        except FileNotFoundError:
            print(
                "Credentials does not exist. Please genrate token.pickle first."
            )

        # Setting lookback timeframe
        self._set_hours_param(event)
        self._calculate_from_to_timestamps()
    def setUp(self):
        self.settings = {
            "workspace_name": "A Company",
            "token": "xxx",
            "base_url": "https://my.service/api",
            "ver_api": 8,
            "base_url_report": "https://my.service/reports/api",
            "ver_report": 2,
        }
        self.api = TogglClientApi(self.settings)
        httpretty.enable()  # enable HTTPretty so that it will monkey patch the socket module

        self.base_api_url = self.api.build_api_url(self.settings["base_url"], self.settings["ver_api"])
        self.base_api_report_url = self.api.build_api_url(self.settings["base_url_report"], self.settings["ver_report"])
Exemplo n.º 6
0
class TogglClientApiLiveTests(unittest.TestCase):

    api = None

    # Runs before *every* tests
    def setUp(self):
        file_contents = open('tests_live_config.json')
        self.settings = json.load(file_contents)
        file_contents.close()

        self.api = TogglClientApi(self.settings)

    # Runs after *every* tests
    def tearDown(self):
        del self.api

    def test_api_client_instance_created(self):
        self.assertNotEqual(self.api, None)

    def test_valid_toggl_base_url(self):
        self.assertEqual(self.api.api_base_url, 'https://www.toggl.com/api/v8')

    def test_api_toggl_auth_check_response_ok(self):
        response = self.api.query('/me')
        self.assertEqual(response.status_code, 200)

    def test_api_toggl_get_list_of_workspaces_response_ok(self):
        response = self.api.get_workspaces()
        self.assertEqual(response.status_code, 200)

    def test_api_toggl_get_workspace_by_name_found(self):
        workspace = self.api.get_workspace_by_name(self.settings['workspace_name'])
        self.assertNotEqual(workspace['id'], '')

    def test_api_toggl_get_workspace_members_response_ok(self):
        workspace = self.api.get_workspace_by_name(self.settings['workspace_name'])
        response = self.api.get_workspace_members(workspace['id'])
        self.assertEqual(response.status_code, 200)

    def test_api_toggl_get_member_total_hours_range_response_ok(self):
        workspace_id = self.settings['workspace_id']
        user_id = self.settings['user_id']
        start_date = self.settings['start_date']
        end_date = self.settings['end_date']
        total = self.api.get_user_hours_range(
            'toggl-python-api-client-nosetests',
            workspace_id,
            user_id,
            start_date,
            end_date
        )

        self.assertGreater(total, 0)
class ToogleClientApiTests(unittest.TestCase):

    api = None
    connectLive = False
    base_api_url = ""
    base_api_report_url = ""
    http_content_type = "application/json"

    # Runs before *every* tests
    def setUp(self):
        self.settings = {
            "workspace_name": "A Company",
            "token": "xxx",
            "base_url": "https://my.service/api",
            "ver_api": 8,
            "base_url_report": "https://my.service/reports/api",
            "ver_report": 2,
        }
        self.api = TogglClientApi(self.settings)
        httpretty.enable()  # enable HTTPretty so that it will monkey patch the socket module

        self.base_api_url = self.api.build_api_url(self.settings["base_url"], self.settings["ver_api"])
        self.base_api_report_url = self.api.build_api_url(self.settings["base_url_report"], self.settings["ver_report"])

    # Runs after *every* tests
    def tearDown(self):
        del self.api
        httpretty.disable()
        httpretty.reset()

    def load_json_file(self, location, base_path="tests/json_responses"):
        file_contents = open(base_path + "/" + location + ".json")
        json_data = json.load(file_contents)
        file_contents.close()

        return json_data

    def test_api_client_instance_created(self):
        self.assertNotEqual(self.api, None)

    def test_overriding_default_base_url_and_version_on_instance_creation(self):
        my_base_url = "http://myownapi.com"
        my_ver_no = 12
        credentials = {"base_url": my_base_url, "ver_api": my_ver_no}
        self.api = TogglClientApi(credentials)
        self.assertEqual(self.api.api_base_url, my_base_url + "/v" + str(my_ver_no))

    def test_api_token_set(self):
        self.assertNotEqual(self.api.api_token, "")

    def test_api_toggl_auth_response(self):
        expected_response_json_str = json.dumps(self.load_json_file("me"))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/me",
            body=expected_response_json_str,
            content_type=self.http_content_type,
        )
        response = self.api.query("/me")

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.text, expected_response_json_str)

    def setup_test_api_toggl_get_workspace(self, json_file="workspaces"):
        expected_response_json_str = json.dumps(self.load_json_file(json_file))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/workspaces",
            body=expected_response_json_str,
            content_type=self.http_content_type,
        )

        return expected_response_json_str

    def test_api_toggl_get_list_of_workspaces_response(self):
        expected_response_json_str = self.setup_test_api_toggl_get_workspace()

        response = self.api.get_workspaces()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.text, expected_response_json_str)

    def setup_test_api_toggl_get_workspace_by_name(self, expected_found_index=2, json_file="workspaces"):
        expected_response_json_str = self.setup_test_api_toggl_get_workspace(json_file)
        data = json.loads(expected_response_json_str)
        expected_workspace = data[expected_found_index]
        workspace = self.api.get_workspace_by_name(self.settings["workspace_name"])

        return workspace, expected_workspace

    def test_api_toggl_get_workspace_by_name_found(self):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()
        self.assertEqual(workspace["id"], expected_workspace["id"])

    def setup_test_api_toggl_get_workspace_members(self):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()

        expected_response_json_str = json.dumps(self.load_json_file("workspace_members"))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/workspaces/" + str(workspace["id"]) + "/workspace_users",
            body=expected_response_json_str,
            content_type=self.http_content_type,
        )

        response = self.api.get_workspace_members(workspace["id"])

        return response, expected_response_json_str, workspace, expected_workspace

    def test_api_toggl_get_workspace_members_response_ok(self):
        response, expected_response_json_str, workspace, expected_workspace = (
            self.setup_test_api_toggl_get_workspace_members()
        )
        self.assertEqual(workspace["id"], expected_workspace["id"])
        self.assertEqual(response.status_code, 200)

    def test_api_toggl_get_workspace_members_response_count(self):
        response, expected_response_json_str, workspace, expected_workspace = (
            self.setup_test_api_toggl_get_workspace_members()
        )

        self.assertEqual(workspace["id"], expected_workspace["id"])

        received_data = response.json()
        expected_data = json.loads(expected_response_json_str)

        self.assertEqual(len(received_data), len(expected_data))

    def test_api_toggl_get_member_total_hours_range_response_ok_with_found_data(self):
        self.do_test_api_toggl_get_member_total_hours_range_response_ok("report_user_project_hours")

    def test_api_toggl_get_member_total_hours_range_response_ok_with_empty_data(self):
        self.do_test_api_toggl_get_member_total_hours_range_response_ok("report_user_project_hours_null")

    def do_test_api_toggl_get_member_total_hours_range_response_ok(self, datafile="report_user_project_hours"):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()
        self.assertEqual(workspace["id"], expected_workspace["id"])

        workspace_id = workspace["id"]
        user_id = 222222
        start_date = "2014-03-03"
        end_date = "2014-03-07"
        user_agent = "toggl-python-api-client-nosetests"

        endpoint_url = (
            self.base_api_report_url
            + "/summary?"
            + "workspace_id="
            + str(workspace["id"])
            + "&since="
            + start_date
            + "&until="
            + end_date
            + "&user_agent="
            + user_agent
            + "&user_ids="
            + str(user_id)
            + "&grouping=users"
            + "&subgrouping=projects"
        )

        expected_response_json_str = json.dumps(self.load_json_file(datafile))
        httpretty.register_uri(
            httpretty.GET, endpoint_url, body=expected_response_json_str, content_type=self.http_content_type
        )

        expected_response_json = json.loads(expected_response_json_str)
        if len(expected_response_json["data"]) > 0:
            expected_total = expected_response_json["data"][0]["time"]
        else:
            expected_total = 0

        total = self.api.get_user_hours_range(user_agent, workspace_id, user_id, start_date, end_date)

        self.assertEqual(total, expected_total)
 def test_overriding_default_base_url_and_version_on_instance_creation(self):
     my_base_url = "http://myownapi.com"
     my_ver_no = 12
     credentials = {"base_url": my_base_url, "ver_api": my_ver_no}
     self.api = TogglClientApi(credentials)
     self.assertEqual(self.api.api_base_url, my_base_url + "/v" + str(my_ver_no))
Exemplo n.º 9
0
    def setUp(self):
        file_contents = open('tests_live_config.json')
        self.settings = json.load(file_contents)
        file_contents.close()

        self.api = TogglClientApi(self.settings)
class Calender2Toggl():
    def __init__(self, look_back_hours: int = 8, event=None) -> None:
        self.look_back_hours = look_back_hours
        self.time_from: str = None  # Format 2021-02-13T08:27:13.772498Z
        self.time_to: str = None
        self.timezone: timezone = timezone("Europe/Budapest")

        # Load credentials
        try:
            with open('token.pickle', 'rb') as token:
                self.creds, self.toogle_settings = pickle.load(token)
                self.toggl_client = TogglClientApi(self.toogle_settings)
        except FileNotFoundError:
            print(
                "Credentials does not exist. Please genrate token.pickle first."
            )

        # Setting lookback timeframe
        self._set_hours_param(event)
        self._calculate_from_to_timestamps()

    def _set_hours_param(self, event=None):
        """ Sets look_back_hours parameter from Pub/Sub trigger message"""
        try:
            if 'data' in event:
                self.look_back_hours = int(
                    base64.b64decode(event['data']).decode('utf-8'))
        except Exception:
            pass

    def _calculate_from_to_timestamps(self) -> None:
        """Calculates date ranges within calendar events will be querried."""
        now = datetime.datetime.utcnow()
        time_to = now.isoformat() + 'Z'  # 'Z' indicates UTC time
        time_from = (now - datetime.timedelta(hours=self.look_back_hours)).isoformat() + \
            'Z'  # 'Z' indicates UTC time
        self.time_from, self.time_to = time_from, time_to

    def _get_toggl_projects(self):
        return self.toggl_client.get_projects().json()

    def _get_calendar_events(self) -> List[Dict]:
        """Querries events from Google Calendar

        Returns:
            event [List[Dict]]: Google Calendar events as list of dicts.
        """

        service = build('calendar',
                        'v3',
                        credentials=self.creds,
                        cache_discovery=False)

        cal_events_result = service.events().list(
            calendarId='primary',
            timeMin=self.time_from,
            timeMax=self.time_to,
            maxResults=None,
            singleEvents=True,
            orderBy='startTime').execute()
        return cal_events_result.get('items', [])

    def _query_existing_toggl_items(self) -> List[Dict]:
        """Query existing time entries in toggl to avoid duplicates"""

        auth = HTTPBasicAuth(self.toogle_settings['token'], 'api_token')
        end_tm = datetime.datetime.now().astimezone(
            self.timezone).replace(microsecond=0)
        start_tm = end_tm - datetime.timedelta(hours=self.look_back_hours + 3)

        url_schema = {
            "start_date": start_tm.isoformat(),
            "end_date": end_tm.isoformat()
        }
        url = "https://api.track.toggl.com/api/v8/time_entries?" + urlencode(
            url_schema)

        return requests.get(url, auth=auth).json()

    @staticmethod
    def load_event(row, toggl_client):
        """Parses and loads events to toggl in the right format.
        """
        try:
            new_entry = {
                "time_entry": {
                    "description": row['description'],
                    "tags": [],
                    "duration": (row['end_tm'] - row['start_tm']).seconds,
                    "start": row['start_tm'].isoformat(),
                    "stop": row['end_tm'].isoformat(),
                    "pid": row['id'],
                    "created_with": "calendar2toggl_app"
                }
            }

            toggl_client.create_time_entry(new_entry)
            print(f"Event loaded: {row['summary']}")
            return 'uploaded'
        except Exception as e:
            print(f"Failed to load: {row}")
            print(e)
            return 'failed to upload'

    def load_to_toggl(self, calendar_events: pd.DataFrame) -> None:
        """Uploads calendar events to toggl

        Args:
            calendar_events (pd.DataFrame): dataframe containing events to be uploaded
        """

        # Load events to toogle
        if calendar_events.shape[0] > 0:
            calendar_events.apply(self.load_event,
                                  axis=1,
                                  args=[self.toggl_client])
        else:
            print(
                f'No events found between {self.time_from} and {self.time_to}.'
            )
class ToogleClientApiTests(unittest.TestCase):

    api = None
    connectLive = False
    base_api_url = ''
    base_api_report_url = ''
    http_content_type = "application/json"

    # Runs before *every* tests
    def setUp(self):
        self.settings = {
            'workspace_name': 'A Company',
            'token': 'xxx',
            'base_url': 'https://my.service/api',
            'ver_api': 8,
            'base_url_report': 'https://my.service/reports/api',
            'ver_report': 2
        }
        self.api = TogglClientApi(self.settings)
        httpretty.enable()  # enable HTTPretty so that it will monkey patch the socket module

        self.base_api_url = self.api.build_api_url(self.settings['base_url'], self.settings['ver_api'])
        self.base_api_report_url = self.api.build_api_url(self.settings['base_url_report'], self.settings['ver_report'])

    # Runs after *every* tests
    def tearDown(self):
        del self.api
        httpretty.disable()
        httpretty.reset()

    def load_json_file(self, location, base_path='tests/json_responses'):
        file_contents = open(base_path+'/'+location+'.json')
        json_data = json.load(file_contents)
        file_contents.close()

        return json_data

    def test_api_client_instance_created(self):
        self.assertNotEqual(self.api, None)

    def test_overriding_default_base_url_and_version_on_instance_creation(self):
        my_base_url = 'http://myownapi.com'
        my_ver_no = 12
        credentials = {
            'base_url': my_base_url,
            'ver_api': my_ver_no
        }
        self.api = TogglClientApi(credentials)
        self.assertEqual(self.api.api_base_url, my_base_url + '/v' + str(my_ver_no))

    def test_api_token_set(self):
        self.assertNotEqual(self.api.api_token, '')

    def test_api_toggl_auth_response(self):
        expected_response_json_str = json.dumps(self.load_json_file('me'))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/me",
            body=expected_response_json_str,
            content_type=self.http_content_type)
        response = self.api.query('/me')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.text, expected_response_json_str)

    def setup_test_api_toggl_get_workspace(self, json_file='workspaces'):
        expected_response_json_str = json.dumps(self.load_json_file(json_file))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/workspaces",
            body=expected_response_json_str,
            content_type=self.http_content_type)

        return expected_response_json_str

    def test_api_toggl_get_list_of_workspaces_response(self):
        expected_response_json_str = self.setup_test_api_toggl_get_workspace()

        response = self.api.get_workspaces()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.text, expected_response_json_str)

    def setup_test_api_toggl_get_workspace_by_name(self, expected_found_index=2, json_file='workspaces'):
        expected_response_json_str = self.setup_test_api_toggl_get_workspace(json_file)
        data = json.loads(expected_response_json_str)
        expected_workspace = data[expected_found_index]
        workspace = self.api.get_workspace_by_name(self.settings['workspace_name'])

        return workspace, expected_workspace

    def test_api_toggl_get_workspace_by_name_found(self):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()
        self.assertEqual(workspace['id'], expected_workspace['id'])

    def setup_test_api_toggl_get_workspace_members(self):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()

        expected_response_json_str = json.dumps(self.load_json_file('workspace_members'))
        httpretty.register_uri(
            httpretty.GET,
            self.base_api_url + "/workspaces/" + str(workspace['id']) + "/workspace_users",
            body=expected_response_json_str,
            content_type=self.http_content_type)

        response = self.api.get_workspace_members(workspace['id'])

        return response, expected_response_json_str, workspace, expected_workspace

    def test_api_toggl_get_workspace_members_response_ok(self):
        response, expected_response_json_str, workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_members()
        self.assertEqual(workspace['id'], expected_workspace['id'])
        self.assertEqual(response.status_code, 200)

    def test_api_toggl_get_workspace_members_response_count(self):
        response, expected_response_json_str, workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_members()

        self.assertEqual(workspace['id'], expected_workspace['id'])

        received_data = response.json()
        expected_data = json.loads(expected_response_json_str)

        self.assertEqual(len(received_data), len(expected_data))

    def test_api_toggl_get_member_total_hours_range_response_ok_with_found_data(self):
        self.do_test_api_toggl_get_member_total_hours_range_response_ok('report_user_project_hours')

    def test_api_toggl_get_member_total_hours_range_response_ok_with_empty_data(self):
        self.do_test_api_toggl_get_member_total_hours_range_response_ok('report_user_project_hours_null')

    def do_test_api_toggl_get_member_total_hours_range_response_ok(self, datafile='report_user_project_hours'):
        workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name()
        self.assertEqual(workspace['id'], expected_workspace['id'])

        workspace_id = workspace['id']
        user_id = 222222
        start_date = '2014-03-03'
        end_date = '2014-03-07'
        user_agent = 'toggl-python-api-client-nosetests'

        endpoint_url = self.base_api_report_url + "/summary?" + "workspace_id=" + str(workspace['id']) + "&since=" + start_date + "&until=" + end_date + "&user_agent=" + user_agent + "&user_ids=" + str(user_id) + "&grouping=users" + "&subgrouping=projects"

        expected_response_json_str = json.dumps(self.load_json_file(datafile))
        httpretty.register_uri(
            httpretty.GET,
            endpoint_url,
            body=expected_response_json_str,
            content_type=self.http_content_type)

        expected_response_json = json.loads(expected_response_json_str)
        if len(expected_response_json['data']) > 0:
            expected_total = expected_response_json['data'][0]['time']
        else:
            expected_total = 0

        total = self.api.get_user_hours_range(
            user_agent,
            workspace_id,
            user_id,
            start_date,
            end_date
        )

        self.assertEqual(total, expected_total)
Exemplo n.º 12
0
 def get_client(self):
     settings = self.get_credentials()
     return TogglClientApi(settings)