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 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(

View file

@ -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,
) )

View file

@ -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>

View file

@ -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
*/ */