-
Notifications
You must be signed in to change notification settings - Fork 1
[feature]: Add Zendesk Workforce Management (WFM) SDK entry point #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class FetchShiftsCmd: | ||
| startDate: str | ||
|
Check warning on line 6 in libzapi/application/commands/wfm/shift_cmds.py
|
||
| endDate: str | ||
|
Check warning on line 7 in libzapi/application/commands/wfm/shift_cmds.py
|
||
| agentIds: list[int] | None = None | ||
|
Check warning on line 8 in libzapi/application/commands/wfm/shift_cmds.py
|
||
| published: int | None = None | ||
| page: int = 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class CreateTeamCmd: | ||
| name: str | ||
| description: str | ||
| manager_id: int | ||
| agents_ids: list[str] | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class UpdateTeamCmd: | ||
| name: str | None = None | ||
| description: str | None = None | ||
| manager_id: int | None = None | ||
| agents_ids: list[str] | None = None | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class BulkAgentsCmd: | ||
| agent_ids: list[str] | ||
| team_ids: list[str] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| from dataclasses import dataclass, field | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class ImportTimeOffEntry: | ||
| agentId: int | ||
|
Check warning on line 6 in libzapi/application/commands/wfm/time_off_cmds.py
|
||
| startTime: int | ||
|
Check warning on line 7 in libzapi/application/commands/wfm/time_off_cmds.py
|
||
| endTime: int | ||
|
Check warning on line 8 in libzapi/application/commands/wfm/time_off_cmds.py
|
||
| reasonId: str | ||
|
Check warning on line 9 in libzapi/application/commands/wfm/time_off_cmds.py
|
||
| id: str | None = None | ||
| note: str | None = None | ||
| status: str | None = None | ||
| timeOffType: str | None = None | ||
|
Check warning on line 13 in libzapi/application/commands/wfm/time_off_cmds.py
|
||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class ImportTimeOffCmd: | ||
| data: list[ImportTimeOffEntry] = field(default_factory=list) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import libzapi.infrastructure.api_clients.wfm as api | ||
| from libzapi.application.services.wfm.activities_service import ActivitiesService | ||
| from libzapi.application.services.wfm.reports_service import ReportsService | ||
| from libzapi.application.services.wfm.shifts_service import ShiftsService | ||
| from libzapi.application.services.wfm.teams_service import TeamsService | ||
| from libzapi.application.services.wfm.time_off_service import TimeOffService | ||
| from libzapi.infrastructure.http.auth import api_token_headers, oauth_headers | ||
| from libzapi.infrastructure.http.client import HttpClient | ||
|
|
||
|
|
||
| class WorkforceManagement: | ||
| def __init__( | ||
| self, base_url: str, oauth_token: str | None = None, email: str | None = None, api_token: str | None = None | ||
| ): | ||
| if oauth_token: | ||
| headers = oauth_headers(oauth_token) | ||
| elif email and api_token: | ||
| headers = api_token_headers(email, api_token) | ||
| else: | ||
| raise ValueError("Provide oauth_token or email+api_token") | ||
|
|
||
| http = HttpClient(base_url, headers=headers) | ||
|
|
||
| self.activities = ActivitiesService(api.ActivityApiClient(http)) | ||
| self.reports = ReportsService(api.ReportApiClient(http)) | ||
| self.shifts = ShiftsService(api.ShiftApiClient(http)) | ||
| self.time_off = TimeOffService(api.TimeOffApiClient(http)) | ||
| self.teams = TeamsService(api.TeamApiClient(http)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Iterable | ||
|
|
||
| from libzapi.domain.models.wfm.activity import Activity, ActivityTypeRef, AgentRef | ||
| from libzapi.infrastructure.api_clients.wfm import ActivityApiClient | ||
|
|
||
|
|
||
| class ActivitiesService: | ||
| def __init__(self, client: ActivityApiClient) -> None: | ||
| self._client = client | ||
|
|
||
| def list(self, start_time: int) -> Iterable[Activity]: | ||
| return self._client.list(start_time=start_time) | ||
|
|
||
| def list_with_relationships(self, start_time: int) -> tuple[list[Activity], list[AgentRef], list[ActivityTypeRef]]: | ||
| return self._client.list_with_relationships(start_time=start_time) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from typing import Iterable | ||
|
|
||
| from libzapi.domain.models.wfm.report import ReportRow | ||
| from libzapi.infrastructure.api_clients.wfm import ReportApiClient | ||
|
|
||
|
|
||
| class ReportsService: | ||
| def __init__(self, client: ReportApiClient) -> None: | ||
| self._client = client | ||
|
|
||
| def get_data(self, template_id: str, start_time: int, end_time: int) -> Iterable[ReportRow]: | ||
| return self._client.get_data(template_id=template_id, start_time=start_time, end_time=end_time) | ||
|
|
||
| def get_data_with_relationships(self, template_id: str, start_time: int, end_time: int) -> dict: | ||
| return self._client.get_data_with_relationships( | ||
| template_id=template_id, start_time=start_time, end_time=end_time | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Iterable | ||
|
|
||
| from libzapi.application.commands.wfm.shift_cmds import FetchShiftsCmd | ||
| from libzapi.domain.models.wfm.shift import Shift | ||
| from libzapi.infrastructure.api_clients.wfm import ShiftApiClient | ||
|
|
||
|
|
||
| class ShiftsService: | ||
| def __init__(self, client: ShiftApiClient) -> None: | ||
| self._client = client | ||
|
|
||
| def fetch( | ||
| self, | ||
| start_date: str, | ||
| end_date: str, | ||
| agent_ids: list[int] | None = None, | ||
| published: int | None = None, | ||
| page: int = 1, | ||
| ) -> Iterable[Shift]: | ||
| cmd = FetchShiftsCmd( | ||
| startDate=start_date, | ||
| endDate=end_date, | ||
| agentIds=agent_ids, | ||
| published=published, | ||
| page=page, | ||
| ) | ||
| return self._client.fetch(cmd=cmd) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Iterable | ||
|
|
||
| from libzapi.application.commands.wfm.team_cmds import BulkAgentsCmd, CreateTeamCmd, UpdateTeamCmd | ||
| from libzapi.domain.models.wfm.team import BulkAgentsResult, Team | ||
| from libzapi.infrastructure.api_clients.wfm import TeamApiClient | ||
|
|
||
|
|
||
| class TeamsService: | ||
| def __init__(self, client: TeamApiClient) -> None: | ||
| self._client = client | ||
|
|
||
| def list(self, deleted: bool = False) -> Iterable[Team]: | ||
| return self._client.list(deleted=deleted) | ||
|
|
||
| def get(self, team_id: str) -> Team: | ||
| return self._client.get(team_id=team_id) | ||
|
|
||
| def create(self, name: str, description: str, manager_id: int, agents_ids: list[str]) -> Team: | ||
| cmd = CreateTeamCmd(name=name, description=description, manager_id=manager_id, agents_ids=agents_ids) | ||
| return self._client.create(cmd=cmd) | ||
|
|
||
| def update(self, team_id: str, **kwargs) -> Team: | ||
| cmd = UpdateTeamCmd(**kwargs) | ||
| return self._client.update(team_id=team_id, cmd=cmd) | ||
|
|
||
| def delete(self, team_id: str) -> None: | ||
| self._client.delete(team_id=team_id) | ||
|
|
||
| def restore(self, team_id: str) -> Team: | ||
| return self._client.restore(team_id=team_id) | ||
|
|
||
| def bulk_add_agents(self, agent_ids: list[str], team_ids: list[str]) -> BulkAgentsResult: | ||
| cmd = BulkAgentsCmd(agent_ids=agent_ids, team_ids=team_ids) | ||
| return self._client.bulk_add_agents(cmd=cmd) | ||
|
|
||
| def bulk_remove_agents(self, agent_ids: list[str], team_ids: list[str]) -> BulkAgentsResult: | ||
| cmd = BulkAgentsCmd(agent_ids=agent_ids, team_ids=team_ids) | ||
| return self._client.bulk_remove_agents(cmd=cmd) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Iterable | ||
|
|
||
| from libzapi.application.commands.wfm.time_off_cmds import ImportTimeOffCmd, ImportTimeOffEntry | ||
| from libzapi.domain.models.wfm.time_off import TimeOff, TimeOffImportResult | ||
| from libzapi.infrastructure.api_clients.wfm import TimeOffApiClient | ||
|
|
||
|
|
||
| class TimeOffService: | ||
| def __init__(self, client: TimeOffApiClient) -> None: | ||
| self._client = client | ||
|
|
||
| def list( | ||
| self, | ||
| time_off_request_id: str | None = None, | ||
| agent_id: int | None = None, | ||
| start_time: int | None = None, | ||
| end_time: int | None = None, | ||
| status: str | None = None, | ||
| reason_id: str | None = None, | ||
| time_off_type: str | None = None, | ||
| page: int | None = None, | ||
| per_page: int | None = None, | ||
| ) -> Iterable[TimeOff]: | ||
| return self._client.list( | ||
| time_off_request_id=time_off_request_id, | ||
| agent_id=agent_id, | ||
| start_time=start_time, | ||
| end_time=end_time, | ||
| status=status, | ||
| reason_id=reason_id, | ||
| time_off_type=time_off_type, | ||
| page=page, | ||
| per_page=per_page, | ||
| ) | ||
|
|
||
| def import_time_off(self, entries: list[ImportTimeOffEntry]) -> TimeOffImportResult: | ||
| cmd = ImportTimeOffCmd(data=entries) | ||
| return self._client.import_time_off(cmd=cmd) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| from dataclasses import dataclass, field | ||
|
|
||
| from libzapi.domain.shared_objects.logical_key import LogicalKey | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class AgentRef: | ||
| id: int | ||
| name: str | ||
| email: str | ||
| deactivated: bool = False | ||
| isDeleted: bool = False | ||
|
Check warning on line 12 in libzapi/domain/models/wfm/activity.py
|
||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class ActivityTypeRef: | ||
| id: str | ||
| name: str | ||
| color: str = "" | ||
| isDeleted: bool = False | ||
|
Check warning on line 20 in libzapi/domain/models/wfm/activity.py
|
||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class Activity: | ||
| id: str | ||
| agentId: int | ||
|
Check warning on line 26 in libzapi/domain/models/wfm/activity.py
|
||
| startTime: int | ||
|
Check warning on line 27 in libzapi/domain/models/wfm/activity.py
|
||
| type: str | ||
| name: str = "" | ||
| ticketId: int | None = None | ||
|
Check warning on line 30 in libzapi/domain/models/wfm/activity.py
|
||
| endTime: int | None = None | ||
|
Check warning on line 31 in libzapi/domain/models/wfm/activity.py
|
||
| duration: int | None = None | ||
| activityTypeIds: list[str] = field(default_factory=list) | ||
|
Check warning on line 33 in libzapi/domain/models/wfm/activity.py
|
||
| eventType: str = "" | ||
|
Check warning on line 34 in libzapi/domain/models/wfm/activity.py
|
||
| color: str = "" | ||
| isPaid: bool = False | ||
|
Check warning on line 36 in libzapi/domain/models/wfm/activity.py
|
||
| lockIntervals: str | None = None | ||
|
Check warning on line 37 in libzapi/domain/models/wfm/activity.py
|
||
|
|
||
| @property | ||
| def logical_key(self) -> LogicalKey: | ||
| return LogicalKey("wfm_activity", self.id) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from dataclasses import dataclass, field | ||
|
|
||
| from libzapi.domain.shared_objects.logical_key import LogicalKey | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class Grouping: | ||
| key: str | ||
| value: str | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class Metric: | ||
| key: str | ||
| value: str | ||
| type: str = "" | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class ReportRow: | ||
| groupings: list[Grouping] = field(default_factory=list) | ||
| metrics: list[Metric] = field(default_factory=list) | ||
|
|
||
| @property | ||
| def logical_key(self) -> LogicalKey: | ||
| keys = "_".join(g.value for g in self.groupings) | ||
| return LogicalKey("wfm_report_row", keys) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from dataclasses import dataclass, field | ||
|
|
||
| from libzapi.domain.shared_objects.logical_key import LogicalKey | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class ShiftTask: | ||
| id: str | ||
| startTime: int | ||
|
Check warning on line 9 in libzapi/domain/models/wfm/shift.py
|
||
| endTime: int | ||
|
Check warning on line 10 in libzapi/domain/models/wfm/shift.py
|
||
| name: str = "" | ||
| color: str = "" | ||
| taskableId: str = "" | ||
|
Check warning on line 13 in libzapi/domain/models/wfm/shift.py
|
||
| taskableType: str = "" | ||
|
Check warning on line 14 in libzapi/domain/models/wfm/shift.py
|
||
| createdAt: str = "" | ||
|
Check warning on line 15 in libzapi/domain/models/wfm/shift.py
|
||
| note: str = "" | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class Shift: | ||
| id: str | ||
| agentId: int | ||
|
Check warning on line 22 in libzapi/domain/models/wfm/shift.py
|
||
| startTime: int | ||
|
Check warning on line 23 in libzapi/domain/models/wfm/shift.py
|
||
| endTime: int | ||
|
Check warning on line 24 in libzapi/domain/models/wfm/shift.py
|
||
| published: bool = False | ||
| parentId: str | int | None = None | ||
|
Check warning on line 26 in libzapi/domain/models/wfm/shift.py
|
||
| tasks: list[ShiftTask] = field(default_factory=list) | ||
|
|
||
| @property | ||
| def logical_key(self) -> LogicalKey: | ||
| return LogicalKey("wfm_shift", self.id) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| from dataclasses import dataclass, field | ||
|
|
||
| from libzapi.domain.shared_objects.logical_key import LogicalKey | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class Team: | ||
| id: str | ||
| name: str | ||
| description: str = "" | ||
| manager_id: int | None = None | ||
| agents_ids: list[str] = field(default_factory=list) | ||
| is_deleted: bool = False | ||
| deleted_at: str | None = None | ||
| tymeshift_account_id: int | None = None | ||
|
|
||
| @property | ||
| def logical_key(self) -> LogicalKey: | ||
| base = self.name.lower().replace(" ", "_") | ||
| return LogicalKey("wfm_team", base) | ||
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class BulkAgentsResult: | ||
| status: str = "" | ||
| affected_teams: list[str] = field(default_factory=list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Atributos não estão em snake case.