mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-19 08:28:10 +02:00
chore: new telephony config as default (#260)
- Mark first new telephony config as default - Show telephony config in campaign details
This commit is contained in:
parent
14bc66d21d
commit
5cfdbeff02
4 changed files with 139 additions and 19 deletions
|
|
@ -7,7 +7,7 @@ Each row represents one provider account that an organization has connected
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from sqlalchemy import update
|
from sqlalchemy import func, update
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
|
|
@ -140,7 +140,14 @@ class TelephonyConfigurationClient(BaseDBClient):
|
||||||
)
|
)
|
||||||
|
|
||||||
async with self.async_session() as session:
|
async with self.async_session() as session:
|
||||||
if is_default_outbound:
|
existing_count = await session.scalar(
|
||||||
|
select(func.count(TelephonyConfigurationModel.id)).where(
|
||||||
|
TelephonyConfigurationModel.organization_id == organization_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if existing_count == 0:
|
||||||
|
is_default_outbound = True
|
||||||
|
elif is_default_outbound:
|
||||||
await self._clear_default_outbound(session, organization_id)
|
await self._clear_default_outbound(session, organization_id)
|
||||||
|
|
||||||
row = TelephonyConfigurationModel(
|
row = TelephonyConfigurationModel(
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,8 @@ class CampaignResponse(BaseModel):
|
||||||
total_queued_count: int = 0
|
total_queued_count: int = 0
|
||||||
parent_campaign_id: Optional[int] = None
|
parent_campaign_id: Optional[int] = None
|
||||||
redialed_campaign_id: Optional[int] = None
|
redialed_campaign_id: Optional[int] = None
|
||||||
|
telephony_configuration_id: Optional[int] = None
|
||||||
|
telephony_configuration_name: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CampaignsResponse(BaseModel):
|
class CampaignsResponse(BaseModel):
|
||||||
|
|
@ -239,6 +241,7 @@ def _build_campaign_response(
|
||||||
workflow_name: str,
|
workflow_name: str,
|
||||||
executed_count: int = 0,
|
executed_count: int = 0,
|
||||||
total_queued_count: int = 0,
|
total_queued_count: int = 0,
|
||||||
|
telephony_configuration_name: Optional[str] = None,
|
||||||
) -> CampaignResponse:
|
) -> CampaignResponse:
|
||||||
"""Build a CampaignResponse from a campaign model."""
|
"""Build a CampaignResponse from a campaign model."""
|
||||||
# Get retry_config from campaign or use defaults
|
# Get retry_config from campaign or use defaults
|
||||||
|
|
@ -293,6 +296,8 @@ def _build_campaign_response(
|
||||||
total_queued_count=total_queued_count,
|
total_queued_count=total_queued_count,
|
||||||
parent_campaign_id=parent_campaign_id,
|
parent_campaign_id=parent_campaign_id,
|
||||||
redialed_campaign_id=redialed_campaign_id,
|
redialed_campaign_id=redialed_campaign_id,
|
||||||
|
telephony_configuration_id=campaign.telephony_configuration_id,
|
||||||
|
telephony_configuration_name=telephony_configuration_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -303,6 +308,22 @@ async def _get_campaign_stats(campaign_id: int) -> tuple[int, int]:
|
||||||
return s.get("executed", 0), s.get("total", 0)
|
return s.get("executed", 0), s.get("total", 0)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_telephony_configuration_name(
|
||||||
|
config_id: Optional[int], organization_id: int
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Resolve the display name for a campaign's telephony configuration.
|
||||||
|
|
||||||
|
Org-scoped lookup so a stale FK from another org (shouldn't happen, but
|
||||||
|
cheap to enforce) doesn't leak across tenants.
|
||||||
|
"""
|
||||||
|
if config_id is None:
|
||||||
|
return None
|
||||||
|
cfg = await db_client.get_telephony_configuration_for_org(
|
||||||
|
config_id, organization_id
|
||||||
|
)
|
||||||
|
return cfg.name if cfg else None
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create")
|
@router.post("/create")
|
||||||
async def create_campaign(
|
async def create_campaign(
|
||||||
request: CreateCampaignRequest,
|
request: CreateCampaignRequest,
|
||||||
|
|
@ -412,7 +433,12 @@ async def create_campaign(
|
||||||
telephony_configuration_id=telephony_configuration_id,
|
telephony_configuration_id=telephony_configuration_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _build_campaign_response(campaign, workflow_name)
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
|
return _build_campaign_response(
|
||||||
|
campaign, workflow_name, telephony_configuration_name=cfg_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
|
|
@ -433,12 +459,22 @@ async def get_campaigns(
|
||||||
[c.id for c in campaigns]
|
[c.id for c in campaigns]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Build {config_id: name} map by fetching all configs for the org once,
|
||||||
|
# rather than one lookup per campaign.
|
||||||
|
org_configs = await db_client.list_telephony_configurations(
|
||||||
|
user.selected_organization_id
|
||||||
|
)
|
||||||
|
config_name_map = {cfg.id: cfg.name for cfg in org_configs}
|
||||||
|
|
||||||
campaign_responses = [
|
campaign_responses = [
|
||||||
_build_campaign_response(
|
_build_campaign_response(
|
||||||
c,
|
c,
|
||||||
workflow_map.get(c.workflow_id, "Unknown"),
|
workflow_map.get(c.workflow_id, "Unknown"),
|
||||||
executed_count=stats_map.get(c.id, {}).get("executed", 0),
|
executed_count=stats_map.get(c.id, {}).get("executed", 0),
|
||||||
total_queued_count=stats_map.get(c.id, {}).get("total", 0),
|
total_queued_count=stats_map.get(c.id, {}).get("total", 0),
|
||||||
|
telephony_configuration_name=config_name_map.get(
|
||||||
|
c.telephony_configuration_id
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for c in campaigns
|
for c in campaigns
|
||||||
]
|
]
|
||||||
|
|
@ -459,8 +495,15 @@ async def get_campaign(
|
||||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||||
|
|
||||||
executed, total = await _get_campaign_stats(campaign.id)
|
executed, total = await _get_campaign_stats(campaign.id)
|
||||||
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
return _build_campaign_response(
|
return _build_campaign_response(
|
||||||
campaign, workflow_name or "Unknown", executed, total
|
campaign,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -502,8 +545,15 @@ async def start_campaign(
|
||||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||||
|
|
||||||
executed, total = await _get_campaign_stats(campaign.id)
|
executed, total = await _get_campaign_stats(campaign.id)
|
||||||
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
return _build_campaign_response(
|
return _build_campaign_response(
|
||||||
campaign, workflow_name or "Unknown", executed, total
|
campaign,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -529,8 +579,15 @@ async def pause_campaign(
|
||||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||||
|
|
||||||
executed, total = await _get_campaign_stats(campaign.id)
|
executed, total = await _get_campaign_stats(campaign.id)
|
||||||
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
return _build_campaign_response(
|
return _build_campaign_response(
|
||||||
campaign, workflow_name or "Unknown", executed, total
|
campaign,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -592,8 +649,15 @@ async def update_campaign(
|
||||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||||
|
|
||||||
executed, total = await _get_campaign_stats(campaign.id)
|
executed, total = await _get_campaign_stats(campaign.id)
|
||||||
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
return _build_campaign_response(
|
return _build_campaign_response(
|
||||||
campaign, workflow_name or "Unknown", executed, total
|
campaign,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -753,7 +817,16 @@ async def redial_campaign(
|
||||||
|
|
||||||
workflow_name = await db_client.get_workflow_name(child.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(child.workflow_id, user.id)
|
||||||
executed, total = await _get_campaign_stats(child.id)
|
executed, total = await _get_campaign_stats(child.id)
|
||||||
return _build_campaign_response(child, workflow_name or "Unknown", executed, total)
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
child.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
|
return _build_campaign_response(
|
||||||
|
child,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{campaign_id}/resume")
|
@router.post("/{campaign_id}/resume")
|
||||||
|
|
@ -794,8 +867,15 @@ async def resume_campaign(
|
||||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||||
|
|
||||||
executed, total = await _get_campaign_stats(campaign.id)
|
executed, total = await _get_campaign_stats(campaign.id)
|
||||||
|
cfg_name = await _get_telephony_configuration_name(
|
||||||
|
campaign.telephony_configuration_id, user.selected_organization_id
|
||||||
|
)
|
||||||
return _build_campaign_response(
|
return _build_campaign_response(
|
||||||
campaign, workflow_name or "Unknown", executed, total
|
campaign,
|
||||||
|
workflow_name or "Unknown",
|
||||||
|
executed,
|
||||||
|
total,
|
||||||
|
telephony_configuration_name=cfg_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,21 @@ export default function CampaignDetailPage() {
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm font-medium">Telephony Configuration</dt>
|
||||||
|
<dd className="mt-1">
|
||||||
|
{campaign.telephony_configuration_id ? (
|
||||||
|
<button
|
||||||
|
onClick={() => router.push(`/telephony-configurations/${campaign.telephony_configuration_id}`)}
|
||||||
|
className="text-blue-600 hover:text-blue-800 hover:underline"
|
||||||
|
>
|
||||||
|
{campaign.telephony_configuration_name || `Configuration #${campaign.telephony_configuration_id}`}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground">Not assigned</span>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm font-medium">State</dt>
|
<dt className="text-sm font-medium">State</dt>
|
||||||
<dd className="mt-1 capitalize">{campaign.state}</dd>
|
<dd className="mt-1 capitalize">{campaign.state}</dd>
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,6 @@ export type AriConfigurationRequest = {
|
||||||
* websocket_client.conf connection name for externalMedia (e.g., dograh_staging)
|
* websocket_client.conf connection name for externalMedia (e.g., dograh_staging)
|
||||||
*/
|
*/
|
||||||
ws_client_name?: string;
|
ws_client_name?: string;
|
||||||
/**
|
|
||||||
* Inbound Workflow Id
|
|
||||||
*
|
|
||||||
* Workflow ID for inbound calls
|
|
||||||
*/
|
|
||||||
inbound_workflow_id?: number | null;
|
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*
|
*
|
||||||
|
|
@ -136,10 +130,6 @@ export type AriConfigurationResponse = {
|
||||||
* Ws Client Name
|
* Ws Client Name
|
||||||
*/
|
*/
|
||||||
ws_client_name?: string;
|
ws_client_name?: string;
|
||||||
/**
|
|
||||||
* Inbound Workflow Id
|
|
||||||
*/
|
|
||||||
inbound_workflow_id?: number | null;
|
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*/
|
*/
|
||||||
|
|
@ -483,6 +473,14 @@ export type CampaignResponse = {
|
||||||
* Redialed Campaign Id
|
* Redialed Campaign Id
|
||||||
*/
|
*/
|
||||||
redialed_campaign_id?: number | null;
|
redialed_campaign_id?: number | null;
|
||||||
|
/**
|
||||||
|
* Telephony Configuration Id
|
||||||
|
*/
|
||||||
|
telephony_configuration_id?: number | null;
|
||||||
|
/**
|
||||||
|
* Telephony Configuration Name
|
||||||
|
*/
|
||||||
|
telephony_configuration_name?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -703,6 +701,12 @@ export type CloudonixConfigurationRequest = {
|
||||||
* Cloudonix Domain ID
|
* Cloudonix Domain ID
|
||||||
*/
|
*/
|
||||||
domain_id: string;
|
domain_id: string;
|
||||||
|
/**
|
||||||
|
* Application Name
|
||||||
|
*
|
||||||
|
* Cloudonix Voice Application name. The application's url is updated when inbound workflows are attached to numbers on this domain.
|
||||||
|
*/
|
||||||
|
application_name: string;
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*
|
*
|
||||||
|
|
@ -729,6 +733,10 @@ export type CloudonixConfigurationResponse = {
|
||||||
* Domain Id
|
* Domain Id
|
||||||
*/
|
*/
|
||||||
domain_id: string;
|
domain_id: string;
|
||||||
|
/**
|
||||||
|
* Application Name
|
||||||
|
*/
|
||||||
|
application_name: string;
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*/
|
*/
|
||||||
|
|
@ -4114,6 +4122,12 @@ export type VobizConfigurationRequest = {
|
||||||
* Vobiz Auth Token
|
* Vobiz Auth Token
|
||||||
*/
|
*/
|
||||||
auth_token: string;
|
auth_token: string;
|
||||||
|
/**
|
||||||
|
* Application Id
|
||||||
|
*
|
||||||
|
* Vobiz Application ID. The application's answer_url is updated when inbound workflows are attached to numbers on this account.
|
||||||
|
*/
|
||||||
|
application_id: string;
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*
|
*
|
||||||
|
|
@ -4140,6 +4154,10 @@ export type VobizConfigurationResponse = {
|
||||||
* Auth Token
|
* Auth Token
|
||||||
*/
|
*/
|
||||||
auth_token: string;
|
auth_token: string;
|
||||||
|
/**
|
||||||
|
* Application Id
|
||||||
|
*/
|
||||||
|
application_id: string;
|
||||||
/**
|
/**
|
||||||
* From Numbers
|
* From Numbers
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue