Fix publish flow and consolidate My Tools view
- Make publish endpoint idempotent: return success if same content already exists instead of VERSION_EXISTS error (fixes retry issues) - Always refresh tools page after publish dialog closes - Consolidate My Tools page to group versions by tool name - Show version count and list instead of separate rows per version - Sum downloads across all versions for accurate totals Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
16de3e371c
commit
74b5124360
|
|
@ -648,8 +648,11 @@ class ToolsPage(QWidget):
|
|||
tool_name = self._current_tool.name # Save before refresh clears it
|
||||
from ..dialogs.publish_dialog import PublishDialog
|
||||
dialog = PublishDialog(self, self._current_tool)
|
||||
if dialog.exec():
|
||||
self.refresh() # Refresh to show updated publish state indicator
|
||||
result = dialog.exec()
|
||||
# Always refresh after dialog closes - tool config may have been updated
|
||||
# even if user canceled (e.g., server processed request before client timeout)
|
||||
self.refresh()
|
||||
if result:
|
||||
self.main_window.show_status(f"Published '{tool_name}'")
|
||||
|
||||
def _sync_tool_status(self):
|
||||
|
|
|
|||
|
|
@ -1797,15 +1797,34 @@ def create_app() -> Flask:
|
|||
return error_response("VALIDATION_ERROR", "Tag exceeds 32 characters")
|
||||
|
||||
owner = g.current_publisher["slug"]
|
||||
|
||||
# Compute config hash early for idempotency check
|
||||
config_hash = compute_yaml_hash(config_text)
|
||||
|
||||
existing = query_one(
|
||||
g.db,
|
||||
"SELECT published_at FROM tools WHERE owner = ? AND name = ? AND version = ?",
|
||||
"SELECT published_at, config_hash, moderation_status, visibility FROM tools WHERE owner = ? AND name = ? AND version = ?",
|
||||
[owner, name, version],
|
||||
)
|
||||
if existing:
|
||||
# If same content (config_hash matches), treat as idempotent retry - return success
|
||||
if existing["config_hash"] == config_hash:
|
||||
return jsonify({
|
||||
"data": {
|
||||
"owner": owner,
|
||||
"name": name,
|
||||
"version": version,
|
||||
"config_hash": config_hash,
|
||||
"pr_url": "",
|
||||
"status": existing["moderation_status"],
|
||||
"visibility": existing["visibility"],
|
||||
"already_published": True,
|
||||
}
|
||||
})
|
||||
# Different content - actual version conflict
|
||||
return error_response(
|
||||
"VERSION_EXISTS",
|
||||
f"Version {version} already exists",
|
||||
f"Version {version} already exists with different content. Use a new version number.",
|
||||
409,
|
||||
details={"published_at": existing["published_at"]},
|
||||
)
|
||||
|
|
@ -1895,8 +1914,7 @@ def create_app() -> Flask:
|
|||
|
||||
tags_json = json.dumps(tags)
|
||||
|
||||
# Compute content hash for integrity verification
|
||||
config_hash = compute_yaml_hash(config_text)
|
||||
# config_hash already computed earlier for idempotency check
|
||||
|
||||
# Determine status based on scrutiny
|
||||
if scrutiny_report and scrutiny_report.get("decision") == "approve":
|
||||
|
|
|
|||
|
|
@ -602,7 +602,32 @@ def dashboard_tools():
|
|||
token = session.get("auth_token")
|
||||
user = _load_current_publisher() or session.get("user", {})
|
||||
status, payload = _api_get("/api/v1/me/tools", token=token)
|
||||
tools = payload.get("data", []) if status == 200 else []
|
||||
all_tools = payload.get("data", []) if status == 200 else []
|
||||
|
||||
# Consolidate multiple versions into single tool entries
|
||||
# Group by tool name, keep latest version as main, collect all versions
|
||||
tools_by_name = {}
|
||||
for tool in all_tools:
|
||||
name = tool.get("name")
|
||||
if name not in tools_by_name:
|
||||
tools_by_name[name] = {
|
||||
**tool,
|
||||
"versions": [tool.get("version")],
|
||||
"all_downloads": tool.get("downloads", 0),
|
||||
}
|
||||
else:
|
||||
# Add this version to the list
|
||||
tools_by_name[name]["versions"].append(tool.get("version"))
|
||||
tools_by_name[name]["all_downloads"] += tool.get("downloads", 0)
|
||||
# Keep the latest version's data (assuming sorted by published_at desc)
|
||||
# The first one we saw is the latest, so no need to update
|
||||
|
||||
# Convert back to list and sort versions for display
|
||||
tools = list(tools_by_name.values())
|
||||
for tool in tools:
|
||||
tool["versions"].sort(key=lambda v: [int(x) for x in v.split(".")], reverse=True)
|
||||
tool["version_count"] = len(tool["versions"])
|
||||
tool["downloads"] = tool["all_downloads"] # Use total across all versions
|
||||
|
||||
# Load rating/issue data for each tool
|
||||
for tool in tools:
|
||||
|
|
|
|||
|
|
@ -69,7 +69,14 @@
|
|||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="text-sm text-gray-900">v{{ tool.version }}</span>
|
||||
{% if tool.version_count and tool.version_count > 1 %}
|
||||
<span class="ml-1 text-xs text-gray-500">(+{{ tool.version_count - 1 }} more)</span>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
{{ tool.versions[:3]|join(', ') }}{% if tool.version_count > 3 %}...{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-xs text-gray-500">{{ tool.published_at|timeago }}</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ tool.downloads|format_number }}
|
||||
|
|
|
|||
Loading…
Reference in New Issue