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:
Abhishek 2026-04-30 17:33:16 +05:30 committed by GitHub
parent 14bc66d21d
commit 5cfdbeff02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 139 additions and 19 deletions

View file

@ -7,7 +7,7 @@ Each row represents one provider account that an organization has connected
from typing import Any, Dict, List, Optional
from sqlalchemy import update
from sqlalchemy import func, update
from sqlalchemy.exc import IntegrityError
from sqlalchemy.future import select
@ -140,7 +140,14 @@ class TelephonyConfigurationClient(BaseDBClient):
)
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)
row = TelephonyConfigurationModel(

View file

@ -194,6 +194,8 @@ class CampaignResponse(BaseModel):
total_queued_count: int = 0
parent_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):
@ -239,6 +241,7 @@ def _build_campaign_response(
workflow_name: str,
executed_count: int = 0,
total_queued_count: int = 0,
telephony_configuration_name: Optional[str] = None,
) -> CampaignResponse:
"""Build a CampaignResponse from a campaign model."""
# Get retry_config from campaign or use defaults
@ -293,6 +296,8 @@ def _build_campaign_response(
total_queued_count=total_queued_count,
parent_campaign_id=parent_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)
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")
async def create_campaign(
request: CreateCampaignRequest,
@ -412,7 +433,12 @@ async def create_campaign(
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("/")
@ -433,12 +459,22 @@ async def get_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 = [
_build_campaign_response(
c,
workflow_map.get(c.workflow_id, "Unknown"),
executed_count=stats_map.get(c.id, {}).get("executed", 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
]
@ -459,8 +495,15 @@ async def get_campaign(
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.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(
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)
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(
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)
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(
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)
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(
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)
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")
@ -794,8 +867,15 @@ async def resume_campaign(
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.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(
campaign, workflow_name or "Unknown", executed, total
campaign,
workflow_name or "Unknown",
executed,
total,
telephony_configuration_name=cfg_name,
)

View file

@ -614,6 +614,21 @@ export default function CampaignDetailPage() {
)}
</dd>
</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>
<dt className="text-sm font-medium">State</dt>
<dd className="mt-1 capitalize">{campaign.state}</dd>

View file

@ -96,12 +96,6 @@ export type AriConfigurationRequest = {
* websocket_client.conf connection name for externalMedia (e.g., dograh_staging)
*/
ws_client_name?: string;
/**
* Inbound Workflow Id
*
* Workflow ID for inbound calls
*/
inbound_workflow_id?: number | null;
/**
* From Numbers
*
@ -136,10 +130,6 @@ export type AriConfigurationResponse = {
* Ws Client Name
*/
ws_client_name?: string;
/**
* Inbound Workflow Id
*/
inbound_workflow_id?: number | null;
/**
* From Numbers
*/
@ -483,6 +473,14 @@ export type CampaignResponse = {
* Redialed Campaign Id
*/
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
*/
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
*
@ -729,6 +733,10 @@ export type CloudonixConfigurationResponse = {
* Domain Id
*/
domain_id: string;
/**
* Application Name
*/
application_name: string;
/**
* From Numbers
*/
@ -4114,6 +4122,12 @@ export type VobizConfigurationRequest = {
* Vobiz Auth Token
*/
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
*
@ -4140,6 +4154,10 @@ export type VobizConfigurationResponse = {
* Auth Token
*/
auth_token: string;
/**
* Application Id
*/
application_id: string;
/**
* From Numbers
*/