diff --git a/tap_zendesk/schemas/archive/README.md b/tap_zendesk/schemas/archive/README.md new file mode 100644 index 00000000..412abdb0 --- /dev/null +++ b/tap_zendesk/schemas/archive/README.md @@ -0,0 +1 @@ +This folder contains not implemented stream schemas \ No newline at end of file diff --git a/tap_zendesk/schemas/archive/talk_availabilities.json b/tap_zendesk/schemas/archive/talk_availabilities.json new file mode 100644 index 00000000..b1b883e0 --- /dev/null +++ b/tap_zendesk/schemas/archive/talk_availabilities.json @@ -0,0 +1,42 @@ +{ + "type": ["object"], + "properties": { + "agent_id": { + "type": [ + "integer", + "null" + ] + }, + "agent_state": { + "type": [ + "string", + "null" + ], + "description": "The availability state of the agent", + "enum": [ + "online", + "offline", + "away" + ] + }, + "call_status": { + "type": [ + "string", + "null" + ], + "description": "The call status of the agent", + "enum": [ + "on_call", + "wrap_up", + "null" + ] + }, + "via": { + "type": [ + "string", + "null" + ], + "description": "The channel (client/phone) the agent is registered to" + } + } +} \ No newline at end of file diff --git a/tap_zendesk/schemas/talk_calls.json b/tap_zendesk/schemas/talk_calls.json new file mode 100644 index 00000000..cb89998b --- /dev/null +++ b/tap_zendesk/schemas/talk_calls.json @@ -0,0 +1,187 @@ +{ + "type": ["object"], + "properties": { + "agent_id": { + "type": ["integer", "null"], + "description": "The id of the first agent who picked up the call" + }, + "call_charge": { + "type": ["string", "null"], + "description": "Total charge for the call. String representation of a decimal number with six decimal places. Example: \"1.230000\". Null if no charge was received from Twilio" + }, + "call_group_id": { + "type": ["integer", "null"], + "description": "The id number of the group the call was last placed in before completion." + }, + "call_recording_consent": { + "type": ["string", "null"], + "description": "Call recording consent value configured for the phone number", + "enum": ["always", "opt_in", "opt_out", "never"] + }, + "call_recording_consent_action": { + "type": ["string", "null"], + "description": "Keypress the caller chose to give their call recording consent option.", + "enum": ["3"] + }, + "callback": { + "type": ["boolean", "null"], + "description": "True if the call was initiated by a callback request from the customer" + }, + "callback_source": { + "type": ["string", "null"], + "description": "The source of the callback request", + "enum": ["queue", "web widget"] + }, + "completion_status": { + "type": ["string", "null"], + "description": "Status of the call", + "enum": ["completed", "abandoned_in_queue", "abandoned_in_ivr", "abandoned_in_voicemail", "abandoned_on_hold", "pending_voicemail"] + }, + "consultation_time": { + "type": ["integer", "null"], + "description": "Sum of how long in seconds agents consulted with each other while the customer was on hold" + }, + "created_at": { + "type": ["string", "null"], + "description": "When the call object was created", + "format": "date-time" + }, + "customer_id": { + "type": ["integer", "null"], + "description": "!!! not documented field !!!" + }, + "customer_requested_voicemail": { + "type": ["boolean"], + "description": "The customer requested to be directed to voicemail instead of waiting for an agent to answer" + }, + "default_group": { + "type": ["boolean", "null"], + "description": "The call was answered by an agent who is a member of the call's default group, if group routing is used" + }, + "direction": { + "type": ["string", "null"], + "description": "Inbound or outbound. The agent or customer who initialized the call", + "enum": ["inbound", "outbound"] + }, + "duration": { + "type": ["integer", "null"], + "description": "Call duration in seconds" + }, + "exceeded_queue_time": { + "type": ["boolean", "null"], + "description": "The customer exceeded the maximum queue wait time and did not speak with an agent" + }, + "hold_time": { + "type": ["integer", "null"], + "description": "Sum of how long in seconds the customer was placed on hold by an agent(s)" + }, + "id": { + "type": ["integer", "null"], + "description": "Call id" + }, + "ivr_action": { + "type": ["string", "null"], + "description": "Menu action that was used by the caller in the IVR menu selection", + "enum": [ + "null", "menu", "voicemail", "group", "phone_number", "textback", "invalid" + ] + }, + "ivr_destination_group_name": { + "type": ["string", "null"], + "description": "Name of the group that received the call through IVR routing. null if IVR is disabled" + }, + "ivr_hops": { + "type": ["integer", "null"], + "description": "How many menu options the customer went through in IVR before talking to an agent. null if IVR is disabled" + }, + "ivr_routed_to": { + "type": ["string", "null"], + "description": "Phone number where call was routed to by IVR. Example: \"+1311123456789\". null if IVR is disabled" + }, + "ivr_time_spent": { + "type": ["integer", "null"], + "description": "How long in seconds the customer spent in IVR. Null if IVR is disabled" + }, + "minutes_billed": { + "type": ["integer", "null"], + "description": "Minutes billed" + }, + "not_recording_time": { + "type": ["integer", "null"], + "description": "How long in seconds spent not recording on the call" + }, + "outside_business_hours": { + "type": ["boolean", "null"], + "description": "The call was received outside business hours" + }, + "overflowed": { + "type": ["boolean", "null"], + "description": "True if the call overflowed" + }, + "overflowed_to": { + "type": ["string", "null"], + "description": "The phone number that the call overflowed to. null if overflowed is false" + }, + "phone_number": { + "type": ["string", "null"], + "description": "Talk phone associated with the call. Example: \"+1311123456789\"" + }, + "phone_number_id": { + "type": ["integer", "null"], + "description": "Talk phone number id" + }, + "quality_issues": { + "type": ["array", "null"], + "description": "A summary of the call's quality issues related to the call provided to Zendesk from Twilio. Until the information is made available by Twilio, the array contains \"information_not_available\". If there are no issues, the array contains \"none\". Other possible values: one or more of \"silence\", \"high_jitter\", \"high_packet_loss\", \"high_pdd\", \"high_latency\"", + "items": { + "type": "string", + "enum": [ + "information_not_available", + "none", + "silence", + "high_jitter", + "high_packet_loss", + "high_pdd", + "high_latency" + ] + } + }, + "recording_control_interactions": { + "type": ["integer", "null"], + "description": "The amount of times agents have paused or resumed a recording on the call." + }, + "recording_time": { + "type": ["integer", "null"], + "description": "How long in seconds spent recording on the call" + }, + "talk_time": { + "type": ["integer", "null"], + "description": "Sum of how long in seconds the customer was in conference with an agent(s). If a call is not accepted by an agent this will be 0" + }, + "ticket_id": { + "type": ["integer", "null"], + "description": "The id of the ticket related to the call" + }, + "time_to_answer": { + "type": ["integer", "null"], + "description": "How long in seconds the customer waited for an agent to answer after hearing the Available agents greeting" + }, + "updated_at": { + "type": ["string", "null"], + "description": "When the call object was last created", + "format": "date-time" + }, + "voicemail": { + "type": ["boolean", "null"], + "description": "If true, the call was a voicemail" + }, + "wait_time": { + "type": ["integer", "null"], + "description": "How long in seconds the customer was in the call before an agent answered" + }, + "wrap_up_time": { + "type": ["integer", "null"], + "description": "Sum of how long in seconds the agent(s) spent in wrap up" + } + } +} \ No newline at end of file diff --git a/tap_zendesk/schemas/talk_phone_numbers.json b/tap_zendesk/schemas/talk_phone_numbers.json new file mode 100644 index 00000000..4977ed71 --- /dev/null +++ b/tap_zendesk/schemas/talk_phone_numbers.json @@ -0,0 +1,180 @@ +{ + "type": ["object"], + "properties": { + "capabilities": { + "type": [ + "null", + "object" + ], + "description": "Whether a phone number has mms, sms, or voice capability", + "properties": { + "sms": { + "type": [ + "null", + "boolean" + ] + }, + "mms": { + "type": [ + "null", + "boolean" + ] + }, + "voice": { + "type": [ + "null", + "boolean" + ] + } + } + }, + "categorised_greetings": { + "properties": { }, + "type": [ + "null", + "object" + ], + "description": "Greeting category ids and names" + }, + "categorised_greetings_with_sub_settings": { + "properties": { }, + "type": [ + "null", + "object" + ], + "description": "The id and any settings associated with each greeting category. If the category has no settings, it defaults to the category name" + }, + "country_code": { + "type": [ + "null", + "string" + ], + "description": "The ISO code of the country for this number" + }, + "created_at": { + "type": [ + "null", + "string" + ], + "format": "date-time", + "description": "The date and time the phone number was created" + }, + "default_greeting_ids": { + "type": ["null", "array"], + "items": { + "type": [ + "string" + ] + }, + "description": "The names of default system greetings associated with the phone number" + }, + "default_group_id": { + "type": [ + "null", + "integer" + ], + "description": "Default group id. *Writeable on most of the plans." + }, + "display_number": { + "type": [ + "null", + "string" + ], + "description": "The formatted phone number" + }, + "external": { + "type": [ + "null", + "boolean" + ] + }, + "greeting_ids": { + "type": ["null", "array"], + "items": { + "type": [ + "integer" + ] + }, + "description": "The external caller id number" + }, + "group_ids": { + "type": ["null", "array"], + "items": { + "type": [ + "integer" + ] + }, + "description": "An array of associated groups. *Writeable on most of plans." + }, + "id": { + "type": [ + "null", + "integer" + ], + "description": "Automatically assigned upon creation" + }, + "location": { + "type": [ + "null", + "string" + ], + "description": "The number's geographical location. For example, \"CA\" or \"Leeds\"" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "The nickname if one is set. Otherwise the display_number" + }, + "nickname": { + "type": [ + "null", + "string" + ], + "description": "The nickname of the number if one is set" + }, + "number": { + "type": [ + "null", + "string" + ], + "description": "The phone number digits" + }, + "recorded": { + "type": [ + "null", + "boolean" + ], + "description": "Whether calls for the number are recorded or not" + }, + "sms_group_id": { + "type": [ + "null", + "integer" + ], + "description": "The group associated with this phone number" + }, + "token": { + "type": [ + "null", + "string" + ], + "description": "A generated token, unique for each phone number and used when provisioning the number. *Writeable on create only." + }, + "toll_free": { + "type": [ + "null", + "boolean" + ], + "description": "Whether the number is toll-free or local" + }, + "transcription": { + "type": [ + "null", + "boolean" + ], + "description": "Whether calls for the number are transcribed or not" + } + } +} \ No newline at end of file diff --git a/tap_zendesk/streams.py b/tap_zendesk/streams.py index 1877d3c5..d953325f 100644 --- a/tap_zendesk/streams.py +++ b/tap_zendesk/streams.py @@ -218,6 +218,7 @@ class Users(Stream): name = "users" replication_method = "INCREMENTAL" replication_key = "updated_at" + endpoint = 'https://{}.zendesk.com/api/v2/users' def _add_custom_fields(self, schema): try: @@ -577,6 +578,7 @@ class TicketForms(Stream): name = "ticket_forms" replication_method = "INCREMENTAL" replication_key = "updated_at" + endpoint = 'https://{}.zendesk.com/api/v2/ticket_forms' def sync(self, state): bookmark = self.get_bookmark(state) @@ -628,6 +630,7 @@ def sync(self, state): class SLAPolicies(Stream): name = "sla_policies" replication_method = "FULL_TABLE" + endpoint = 'https://{}.zendesk.com/api/v2/slas/policies' def sync(self, state): # pylint: disable=unused-argument for policy in self.client.sla_policies(): @@ -639,6 +642,49 @@ def check_access(self): ''' self.client.sla_policies() +class TalkPhoneNumbers(Stream): + name = 'talk_phone_numbers' + replication_method = "FULL_TABLE" + endpoint = 'https://{}.zendesk.com/api/v2/channels/voice/phone_numbers' + + def sync(self, state): # pylint: disable=unused-argument + for phone_number in self.client.talk.phone_numbers(): + yield (self.stream, phone_number) + + +class TalkCalls(CursorBasedExportStream): + name = 'talk_calls' + replication_method = "INCREMENTAL" + replication_key = 'updated_at' + endpoint = 'https://{}.zendesk.com/api/v2/channels/voice/stats/incremental/calls' + + def sync(self, state): + bookmark = self.get_bookmark(state) + + if not bookmark: + start_date = self.config.get('start_date') + if start_date: + bookmark = utils.strptime_with_tz(start_date) + + LOGGER.info(f'Start date: {bookmark}') + + for call in self.client.talk.calls.incremental(start_time=bookmark): + if utils.strptime_with_tz(call.updated_at) >= bookmark: + self.update_bookmark(state, call.updated_at) + yield (self.stream, call) + + def check_access(self): + ''' + Check whether the permission was given to access stream resources or not. + ''' + url = self.endpoint.format(self.config['subdomain']) + # Convert start_date parameter to timestamp to pass with request param + start_time = datetime.datetime.strptime(self.config['start_date'], START_DATE_FORMAT).timestamp() + HEADERS['Authorization'] = 'Bearer {}'.format(self.config["access_token"]) + + http.call_api(url, self.request_timeout, params={'start_time': f'{start_time:.0f}', 'per_page': 1}, headers=HEADERS) + + STREAMS = { "tickets": Tickets, "groups": Groups, @@ -654,4 +700,6 @@ def check_access(self): "tags": Tags, "ticket_metrics": TicketMetrics, "sla_policies": SLAPolicies, + "talk_phone_numbers": TalkPhoneNumbers, + "talk_calls": TalkCalls }