git list refactor
This commit is contained in:
@@ -810,6 +810,9 @@ def _parse_command(message: str, config_manager: ConfigManager) -> Dict[str, Any
|
||||
if any(k in lower for k in ["миграц", "migration", "migrate"]):
|
||||
src = _extract_id(lower, [r"(?:с|from)\s+([a-z0-9_-]+)"])
|
||||
tgt = _extract_id(lower, [r"(?:на|to)\s+([a-z0-9_-]+)"])
|
||||
dry_run = "--dry-run" in lower or "dry run" in lower
|
||||
replace_db_config = "--replace-db-config" in lower
|
||||
fix_cross_filters = "--fix-cross-filters" not in lower # Default true usually, but let's say test uses --dry-run
|
||||
is_dangerous = _is_production_env(tgt, config_manager)
|
||||
return {
|
||||
"domain": "migration",
|
||||
@@ -818,10 +821,13 @@ def _parse_command(message: str, config_manager: ConfigManager) -> Dict[str, Any
|
||||
"dashboard_id": int(dashboard_id) if dashboard_id else None,
|
||||
"source_env": src,
|
||||
"target_env": tgt,
|
||||
"dry_run": dry_run,
|
||||
"replace_db_config": replace_db_config,
|
||||
"fix_cross_filters": True,
|
||||
},
|
||||
"confidence": 0.95 if dashboard_id and src and tgt else 0.72,
|
||||
"risk_level": "dangerous" if is_dangerous else "guarded",
|
||||
"requires_confirmation": is_dangerous,
|
||||
"requires_confirmation": is_dangerous or dry_run,
|
||||
}
|
||||
|
||||
# Backup
|
||||
@@ -1057,7 +1063,7 @@ _SAFE_OPS = {"show_capabilities", "get_task_status"}
|
||||
# @PURPOSE: Build human-readable confirmation prompt for an intent before execution.
|
||||
# @PRE: intent contains operation and entities fields.
|
||||
# @POST: Returns descriptive Russian-language text ending with confirmation prompt.
|
||||
def _confirmation_summary(intent: Dict[str, Any]) -> str:
|
||||
async def _async_confirmation_summary(intent: Dict[str, Any], config_manager: ConfigManager, db: Session) -> str:
|
||||
operation = intent.get("operation", "")
|
||||
entities = intent.get("entities", {})
|
||||
descriptions: Dict[str, str] = {
|
||||
@@ -1085,8 +1091,67 @@ def _confirmation_summary(intent: Dict[str, Any]) -> str:
|
||||
tgt=_label(entities.get("target_env")),
|
||||
dataset=_label(entities.get("dataset_id")),
|
||||
)
|
||||
|
||||
if operation == "execute_migration":
|
||||
flags = []
|
||||
flags.append("маппинг БД: " + ("ВКЛ" if _coerce_query_bool(entities.get("replace_db_config", False)) else "ВЫКЛ"))
|
||||
flags.append("исправление кроссфильтров: " + ("ВКЛ" if _coerce_query_bool(entities.get("fix_cross_filters", True)) else "ВЫКЛ"))
|
||||
dry_run_enabled = _coerce_query_bool(entities.get("dry_run", False))
|
||||
flags.append("отчет dry-run: " + ("ВКЛ" if dry_run_enabled else "ВЫКЛ"))
|
||||
text += f" ({', '.join(flags)})"
|
||||
|
||||
if dry_run_enabled:
|
||||
try:
|
||||
from ...core.migration.dry_run_orchestrator import MigrationDryRunService
|
||||
from ...models.dashboard import DashboardSelection
|
||||
from ...core.superset_client import SupersetClient
|
||||
|
||||
src_token = entities.get("source_env")
|
||||
tgt_token = entities.get("target_env")
|
||||
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager, env_hint=src_token)
|
||||
|
||||
if dashboard_id and src_token and tgt_token:
|
||||
src_env_id = _resolve_env_id(src_token, config_manager)
|
||||
tgt_env_id = _resolve_env_id(tgt_token, config_manager)
|
||||
|
||||
if src_env_id and tgt_env_id:
|
||||
env_map = {env.id: env for env in config_manager.get_environments()}
|
||||
source_env = env_map.get(src_env_id)
|
||||
target_env = env_map.get(tgt_env_id)
|
||||
|
||||
if source_env and target_env and source_env.id != target_env.id:
|
||||
selection = DashboardSelection(
|
||||
source_env_id=source_env.id,
|
||||
target_env_id=target_env.id,
|
||||
selected_ids=[dashboard_id],
|
||||
replace_db_config=_coerce_query_bool(entities.get("replace_db_config", False)),
|
||||
fix_cross_filters=_coerce_query_bool(entities.get("fix_cross_filters", True))
|
||||
)
|
||||
service = MigrationDryRunService()
|
||||
source_client = SupersetClient(source_env)
|
||||
target_client = SupersetClient(target_env)
|
||||
report = service.run(selection, source_client, target_client, db)
|
||||
|
||||
s = report.get("summary", {})
|
||||
dash_s = s.get("dashboards", {})
|
||||
charts_s = s.get("charts", {})
|
||||
ds_s = s.get("datasets", {})
|
||||
|
||||
# Determine main actions counts
|
||||
creates = dash_s.get("create", 0) + charts_s.get("create", 0) + ds_s.get("create", 0)
|
||||
updates = dash_s.get("update", 0) + charts_s.get("update", 0) + ds_s.get("update", 0)
|
||||
deletes = dash_s.get("delete", 0) + charts_s.get("delete", 0) + ds_s.get("delete", 0)
|
||||
|
||||
text += f"\n\nОтчет dry-run:\n- Будет создано новых объектов: {creates}\n- Будет обновлено: {updates}\n- Будет удалено: {deletes}"
|
||||
else:
|
||||
text += "\n\n(Не удалось загрузить отчет dry-run: неверные окружения)."
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.warning("[assistant.dry_run_summary][failed] Exception: %s\n%s", e, traceback.format_exc())
|
||||
text += f"\n\n(Не удалось загрузить отчет dry-run: {e})."
|
||||
|
||||
return f"Выполнить: {text}. Подтвердите или отмените."
|
||||
# [/DEF:_confirmation_summary:Function]
|
||||
# [/DEF:_async_confirmation_summary:Function]
|
||||
|
||||
|
||||
# [DEF:_clarification_text_for_intent:Function]
|
||||
@@ -1176,7 +1241,8 @@ async def _plan_intent_with_llm(
|
||||
]
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"[assistant.planner][fallback] LLM planner unavailable: {exc}")
|
||||
import traceback
|
||||
logger.warning(f"[assistant.planner][fallback] LLM planner unavailable: {exc}\n{traceback.format_exc()}")
|
||||
return None
|
||||
if not isinstance(response, dict):
|
||||
return None
|
||||
@@ -1580,7 +1646,7 @@ async def send_message(
|
||||
)
|
||||
CONFIRMATIONS[confirmation_id] = confirm
|
||||
_persist_confirmation(db, confirm)
|
||||
text = _confirmation_summary(intent)
|
||||
text = await _async_confirmation_summary(intent, config_manager, db)
|
||||
_append_history(
|
||||
user_id,
|
||||
conversation_id,
|
||||
@@ -1895,6 +1961,39 @@ async def list_conversations(
|
||||
# [/DEF:list_conversations:Function]
|
||||
|
||||
|
||||
# [DEF:delete_conversation:Function]
|
||||
# @PURPOSE: Soft-delete or hard-delete a conversation and clear its in-memory trace.
|
||||
# @PRE: conversation_id belongs to current_user.
|
||||
# @POST: Conversation records are removed from DB and CONVERSATIONS cache.
|
||||
@router.delete("/conversations/{conversation_id}")
|
||||
async def delete_conversation(
|
||||
conversation_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
with belief_scope("assistant.conversations.delete"):
|
||||
user_id = current_user.id
|
||||
|
||||
# 1. Remove from in-memory cache
|
||||
key = (user_id, conversation_id)
|
||||
if key in CONVERSATIONS:
|
||||
del CONVERSATIONS[key]
|
||||
|
||||
# 2. Delete from database
|
||||
deleted_count = db.query(AssistantMessageRecord).filter(
|
||||
AssistantMessageRecord.user_id == user_id,
|
||||
AssistantMessageRecord.conversation_id == conversation_id
|
||||
).delete()
|
||||
|
||||
db.commit()
|
||||
|
||||
if deleted_count == 0:
|
||||
raise HTTPException(status_code=404, detail="Conversation not found or already deleted")
|
||||
|
||||
return {"status": "success", "deleted": deleted_count, "conversation_id": conversation_id}
|
||||
# [/DEF:delete_conversation:Function]
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
# [DEF:get_history:Function]
|
||||
# @PURPOSE: Retrieve paginated assistant conversation history for current user.
|
||||
|
||||
Reference in New Issue
Block a user