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
|
tool_name = self._current_tool.name # Save before refresh clears it
|
||||||
from ..dialogs.publish_dialog import PublishDialog
|
from ..dialogs.publish_dialog import PublishDialog
|
||||||
dialog = PublishDialog(self, self._current_tool)
|
dialog = PublishDialog(self, self._current_tool)
|
||||||
if dialog.exec():
|
result = dialog.exec()
|
||||||
self.refresh() # Refresh to show updated publish state indicator
|
# 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}'")
|
self.main_window.show_status(f"Published '{tool_name}'")
|
||||||
|
|
||||||
def _sync_tool_status(self):
|
def _sync_tool_status(self):
|
||||||
|
|
|
||||||
|
|
@ -1797,15 +1797,34 @@ def create_app() -> Flask:
|
||||||
return error_response("VALIDATION_ERROR", "Tag exceeds 32 characters")
|
return error_response("VALIDATION_ERROR", "Tag exceeds 32 characters")
|
||||||
|
|
||||||
owner = g.current_publisher["slug"]
|
owner = g.current_publisher["slug"]
|
||||||
|
|
||||||
|
# Compute config hash early for idempotency check
|
||||||
|
config_hash = compute_yaml_hash(config_text)
|
||||||
|
|
||||||
existing = query_one(
|
existing = query_one(
|
||||||
g.db,
|
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],
|
[owner, name, version],
|
||||||
)
|
)
|
||||||
if existing:
|
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(
|
return error_response(
|
||||||
"VERSION_EXISTS",
|
"VERSION_EXISTS",
|
||||||
f"Version {version} already exists",
|
f"Version {version} already exists with different content. Use a new version number.",
|
||||||
409,
|
409,
|
||||||
details={"published_at": existing["published_at"]},
|
details={"published_at": existing["published_at"]},
|
||||||
)
|
)
|
||||||
|
|
@ -1895,8 +1914,7 @@ def create_app() -> Flask:
|
||||||
|
|
||||||
tags_json = json.dumps(tags)
|
tags_json = json.dumps(tags)
|
||||||
|
|
||||||
# Compute content hash for integrity verification
|
# config_hash already computed earlier for idempotency check
|
||||||
config_hash = compute_yaml_hash(config_text)
|
|
||||||
|
|
||||||
# Determine status based on scrutiny
|
# Determine status based on scrutiny
|
||||||
if scrutiny_report and scrutiny_report.get("decision") == "approve":
|
if scrutiny_report and scrutiny_report.get("decision") == "approve":
|
||||||
|
|
|
||||||
|
|
@ -602,7 +602,32 @@ def dashboard_tools():
|
||||||
token = session.get("auth_token")
|
token = session.get("auth_token")
|
||||||
user = _load_current_publisher() or session.get("user", {})
|
user = _load_current_publisher() or session.get("user", {})
|
||||||
status, payload = _api_get("/api/v1/me/tools", token=token)
|
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
|
# Load rating/issue data for each tool
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,14 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<span class="text-sm text-gray-900">v{{ tool.version }}</span>
|
<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>
|
<p class="text-xs text-gray-500">{{ tool.published_at|timeago }}</p>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ tool.downloads|format_number }}
|
{{ tool.downloads|format_number }}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue