fix: metadata fix for content_hash diffs

This commit is contained in:
2026-04-27 11:08:21 +02:00
parent 56c5ec1861
commit 53dd9deeab
6 changed files with 137 additions and 4 deletions

View File

@@ -119,7 +119,7 @@ defmodule BDS.Desktop.ShellCommands do
defp dispatch("rebuild_posts_from_files", project, _params) do defp dispatch("rebuild_posts_from_files", project, _params) do
queue_task(project, "rebuild_posts_from_files", "Rebuild Posts From Files", "Maintenance", fn report -> queue_task(project, "rebuild_posts_from_files", "Rebuild Posts From Files", "Maintenance", fn report ->
{:ok, posts} = Maintenance.rebuild_from_filesystem(project.id, "post", on_progress: report, rebuild_embeddings: false) {:ok, posts} = Maintenance.rebuild_from_filesystem(project.id, "post", on_progress: report)
report.(1.0, "Post rebuild complete") report.(1.0, "Post rebuild complete")
%{project_id: project.id, counts: %{posts: length(posts)}} %{project_id: project.id, counts: %{posts: length(posts)}}
end) end)

View File

@@ -17,7 +17,11 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
def rerun(socket) do def rerun(socket) do
case meta(socket.assigns) do case meta(socket.assigns) do
%{action: action} when is_binary(action) -> {:command, action} %{action: action} when is_binary(action) -> {:command, action}
_other -> {:noop, socket} _other ->
case misc_route_action(socket.assigns.current_tab.type) do
nil -> {:noop, socket}
action -> {:command, action}
end
end end
end end
@@ -464,7 +468,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
end end
end end
defp metadata_diff_repairable_tab?(tab_id), do: tab_id in ["posts", "media", "scripts", "templates", "project"] defp metadata_diff_repairable_tab?(tab_id), do: tab_id in ["posts", "media", "scripts", "templates", "project", "embeddings"]
defp misc_route_action(:site_validation), do: "validate_site"
defp misc_route_action(:metadata_diff), do: "metadata_diff"
defp misc_route_action(:translation_validation), do: "validate_translations"
defp misc_route_action(:find_duplicates), do: "find_duplicates"
defp misc_route_action(_route), do: nil
defp format_metadata_diff_value(nil), do: "-" defp format_metadata_diff_value(nil), do: "-"
defp format_metadata_diff_value(""), do: "-" defp format_metadata_diff_value(""), do: "-"

View File

@@ -20,6 +20,14 @@ defmodule BDS.Embeddings do
def index_path(project_id), do: Index.path(project_id) def index_path(project_id), do: Index.path(project_id)
def reindex_all(project_id), do: rebuild_project(project_id) def reindex_all(project_id), do: rebuild_project(project_id)
def refresh_snapshot(project_id) when is_binary(project_id) do
if enabled_for_project?(project_id) do
:ok = rebuild_snapshot(project_id)
end
:ok
end
def get_indexing_progress(project_id) when is_binary(project_id) do def get_indexing_progress(project_id) when is_binary(project_id) do
indexed = indexed =
Repo.one( Repo.one(
@@ -105,7 +113,15 @@ defmodule BDS.Embeddings do
if differences == [] do if differences == [] do
[] []
else else
[%{entity_type: "embedding", entity_id: post.id, differences: differences}] [
%{
entity_type: "embedding",
entity_id: post.id,
label: post.title || post.slug || post.id,
meta_label: Persistence.timestamp_to_iso8601(post.created_at),
differences: differences
}
]
end end
end) end)
else else

View File

@@ -664,6 +664,8 @@ defmodule BDS.Maintenance do
{:db_to_file, "script"} -> BDS.Scripts.sync_published_script_file(entity_id) {:db_to_file, "script"} -> BDS.Scripts.sync_published_script_file(entity_id)
{:file_to_db, "template"} -> BDS.Templates.sync_template_from_file(entity_id) {:file_to_db, "template"} -> BDS.Templates.sync_template_from_file(entity_id)
{:db_to_file, "template"} -> BDS.Templates.sync_published_template_file(entity_id) {:db_to_file, "template"} -> BDS.Templates.sync_published_template_file(entity_id)
{:file_to_db, "embedding"} -> BDS.Embeddings.sync_post(entity_id)
{:db_to_file, "embedding"} -> BDS.Embeddings.refresh_snapshot(project_id)
_other -> {:error, :unsupported} _other -> {:error, :unsupported}
end end
end end

View File

@@ -99,6 +99,30 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert is_map(completed.result.payload.summary) assert is_map(completed.result.payload.summary)
end end
test "rebuild_posts_from_files rebuilds embeddings for published posts when semantic similarity is enabled", %{project: project} do
assert {:ok, _metadata} =
BDS.Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})
assert {:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Filesystem Embedding Source",
content: "space rocket orbit mission galaxy",
language: "en"
})
assert {:ok, published_post} = BDS.Posts.publish_post(post.id)
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
BDS.Repo.delete_all(BDS.Embeddings.Key)
assert {:ok, result} = ShellCommands.execute("rebuild_posts_from_files")
completed = wait_for_task(result.task_id, &(&1.status == :completed))
assert completed.group_name == "Maintenance"
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
end
test "repair_metadata_diff exposes live in-task progress from the repair worker", %{project: project} do test "repair_metadata_diff exposes live in-task progress from the repair worker", %{project: project} do
original = Application.get_env(:bds, :tasks, []) original = Application.get_env(:bds, :tasks, [])

View File

@@ -370,6 +370,34 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(class="tab active transient") assert html =~ ~s(class="tab active transient")
end end
test "metadata diff refresh reruns after workbench session restore", %{project: project} do
:ok = BDS.Tasks.clear_finished()
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
session_payload =
Workbench.new()
|> Workbench.open_tab(:metadata_diff, "metadata_diff", :pin)
|> Session.serialize()
html = render_hook(view, "restore_workbench_session", %{"session" => session_payload})
assert html =~ ~s(data-tab-type="metadata_diff")
existing_ids = MapSet.new(Enum.map(BDS.Tasks.list_tasks(), & &1.id))
_html =
view
|> element("button[phx-click='rerun_misc_editor']")
|> render_click()
refresh_task = new_task!(existing_ids, "Metadata Diff")
assert refresh_task.group_name == "Maintenance"
completed_task!(refresh_task.id)
send(view.pid, :refresh_task_status)
assert render(view) =~ project.name
end
test "shell live renders the legacy git activity badge from remote behind count" do test "shell live renders the legacy git activity badge from remote behind count" do
Application.put_env(:bds, :git_remote_state_provider, fn _project_id, _opts -> Application.put_env(:bds, :git_remote_state_provider, fn _project_id, _opts ->
{:ok, %{local_branch: "main", upstream_branch: "origin/main", has_upstream: true, ahead: 0, behind: 7}} {:ok, %{local_branch: "main", upstream_branch: "origin/main", has_upstream: true, ahead: 0, behind: 7}}
@@ -1163,6 +1191,59 @@ defmodule BDS.Desktop.ShellLiveTest do
refute orphan_relative_path in Enum.map(diff.orphan_reports, & &1.file_path) refute orphan_relative_path in Enum.map(diff.orphan_reports, & &1.file_path)
end end
test "metadata diff embeddings tab exposes repair actions and clears embedding drift", %{project: project} do
:ok = BDS.Tasks.clear_finished()
assert {:ok, _metadata} =
Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})
assert {:ok, post} =
Posts.create_post(%{
project_id: project.id,
title: "Embedding Drift",
content: "space rocket orbit mission galaxy",
language: "en"
})
assert {:ok, published_post} = Posts.publish_post(post.id)
assert {:ok, _indexed} = BDS.Embeddings.index_unindexed(project.id)
Repo.delete_all(BDS.Embeddings.Key)
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
assert {:ok, queued} = BDS.Desktop.ShellCommands.execute("metadata_diff")
completed_task!(queued.task_id)
send(view.pid, :refresh_task_status)
html =
view
|> element("[data-testid='metadata-diff-tab'][data-entity-tab='embeddings']")
|> render_click()
assert html =~ "content_hash"
assert html =~ ~s(data-testid="metadata-diff-repair-button")
existing_ids = MapSet.new(Enum.map(BDS.Tasks.list_tasks(), & &1.id))
html =
view
|> element("[data-testid='metadata-diff-repair-button'][data-direction='file_to_db'][data-field='content_hash']")
|> render_click()
assert html =~ "Repair Metadata Diff"
repair_task = new_task!(existing_ids, "Repair Metadata Diff")
completed_task!(repair_task.id)
send(view.pid, :refresh_task_status)
_html = render(view)
assert Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
assert {:ok, diff} = BDS.Maintenance.metadata_diff(project.id)
refute Enum.any?(diff.diff_reports, &(&1.entity_type == "embedding" and &1.entity_id == published_post.id))
end
test "post tabs render a real editor and drive save publish discard flows", %{project: project} do test "post tabs render a real editor and drive save publish discard flows", %{project: project} do
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "alpha", color: "#112233"}) assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "alpha", color: "#112233"})
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "beta", color: "#445566"}) assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "beta", color: "#445566"})