mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +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 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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue