diff --git a/lib/oli/authoring/editing/container_editor.ex b/lib/oli/authoring/editing/container_editor.ex index 2c947fa6711..064f8863e00 100644 --- a/lib/oli/authoring/editing/container_editor.ex +++ b/lib/oli/authoring/editing/container_editor.ex @@ -447,11 +447,12 @@ defmodule Oli.Authoring.Editing.ContainerEditor do A true deep-copy of all the content is made. Resources and Revisions are created instead of simply referenced. """ def duplicate_page( - %Revision{} = container, + container, page_id, %Author{} = author, %Project{} = project - ) do + ) + when is_nil(container) or is_struct(container, Revision) do original_page = Resources.get_revision!(page_id) |> Map.from_struct() diff --git a/lib/oli_web/live/resources/pages_view.ex b/lib/oli_web/live/resources/pages_view.ex index a2d2db8ae5f..f9e18c60a0c 100644 --- a/lib/oli_web/live/resources/pages_view.ex +++ b/lib/oli_web/live/resources/pages_view.ex @@ -118,6 +118,13 @@ defmodule OliWeb.Resources.PagesView do end end + defp parent_container(project, revision) do + case PageBrowse.find_parent_container(project, revision) do + [] -> nil + [container] -> container + end + end + def handle_params(params, _, socket) do table_model = SortableTableModel.update_from_params( @@ -610,43 +617,22 @@ defmodule OliWeb.Resources.PagesView do page_id = String.to_integer(page_id) revision = Enum.find(socket.assigns.table_model.rows, fn r -> r.id == page_id end) - original_page = Map.from_struct(revision) - - new_page_attrs = - original_page - |> Map.drop([:slug, :inserted_at, :updated_at, :resource_id, :resource]) - |> Map.put(:title, "#{original_page.title} (copy)") - |> Map.put(:content, nil) - |> Map.put(:author_id, author.id) - |> then(fn map -> - if is_nil(map.legacy) do - map - else - Map.put(map, :legacy, Map.from_struct(original_page.legacy)) - end - end) + socket = + case revision do + nil -> + put_flash(socket, :error, "Could not duplicate page") - Oli.Repo.transaction(fn -> - with {:ok, %{revision: revision}} <- - Oli.Authoring.Course.create_and_attach_resource(project, new_page_attrs), - {:ok, _} <- Oli.Publishing.ChangeTracker.track_revision(project.slug, revision), - {:ok, model_duplicated_activities} <- - Oli.Authoring.Editing.ContainerEditor.deep_copy_activities( - original_page.content["model"], - project.slug, - author - ), - new_content <- %{ - original_page.content - | "model" => Enum.reverse(model_duplicated_activities) - }, - {:ok, updated_revision} <- - Oli.Resources.update_revision(revision, %{content: new_content}) do - updated_revision - else - {:error, e} -> Oli.Repo.rollback(e) + revision -> + container = parent_container(project, revision) + + case ContainerEditor.duplicate_page(container, page_id, author, project) do + {:ok, _result} -> + socket + + {:error, _reason} -> + put_flash(socket, :error, "Could not duplicate page") + end end - end) patch_with(socket, %{}) end diff --git a/lib/oli_web/live/workspaces/course_author/pages_live.ex b/lib/oli_web/live/workspaces/course_author/pages_live.ex index 9efd704f0c5..9a5f78201fa 100644 --- a/lib/oli_web/live/workspaces/course_author/pages_live.ex +++ b/lib/oli_web/live/workspaces/course_author/pages_live.ex @@ -401,43 +401,22 @@ defmodule OliWeb.Workspaces.CourseAuthor.PagesLive do page_id = String.to_integer(page_id) revision = Enum.find(socket.assigns.table_model.rows, fn r -> r.id == page_id end) - original_page = Map.from_struct(revision) - - new_page_attrs = - original_page - |> Map.drop([:slug, :inserted_at, :updated_at, :resource_id, :resource]) - |> Map.put(:title, "#{original_page.title} (copy)") - |> Map.put(:content, nil) - |> Map.put(:author_id, author.id) - |> then(fn map -> - if is_nil(map.legacy) do - map - else - Map.put(map, :legacy, Map.from_struct(original_page.legacy)) - end - end) + socket = + case revision do + nil -> + put_flash(socket, :error, "Could not duplicate page") + + revision -> + container = parent_container(project, revision) + + case ContainerEditor.duplicate_page(container, page_id, author, project) do + {:ok, _result} -> + socket - Oli.Repo.transaction(fn -> - with {:ok, %{revision: revision}} <- - Oli.Authoring.Course.create_and_attach_resource(project, new_page_attrs), - {:ok, _} <- Oli.Publishing.ChangeTracker.track_revision(project.slug, revision), - {:ok, model_duplicated_activities} <- - Oli.Authoring.Editing.ContainerEditor.deep_copy_activities( - original_page.content["model"], - project.slug, - author - ), - new_content <- %{ - original_page.content - | "model" => Enum.reverse(model_duplicated_activities) - }, - {:ok, updated_revision} <- - Oli.Resources.update_revision(revision, %{content: new_content}) do - updated_revision - else - {:error, e} -> Oli.Repo.rollback(e) + {:error, _reason} -> + put_flash(socket, :error, "Could not duplicate page") + end end - end) patch_with(socket, %{}) end @@ -628,6 +607,13 @@ defmodule OliWeb.Workspaces.CourseAuthor.PagesLive do end end + defp parent_container(project, revision) do + case PageBrowse.find_parent_container(project, revision) do + [] -> nil + [container] -> container + end + end + defp patch_with(socket, changes) do {:noreply, push_patch(socket, diff --git a/test/oli/editing/container_editor_test.exs b/test/oli/editing/container_editor_test.exs index 511a0727050..da68157bacc 100644 --- a/test/oli/editing/container_editor_test.exs +++ b/test/oli/editing/container_editor_test.exs @@ -309,6 +309,88 @@ defmodule Oli.Authoring.Editing.ContainerEditorTest do assert created_activity2.objectives["1"] == activity_revision2.objectives["1"] end + + test "duplicate_page/4 preserves the order of two activities in a basic page", %{ + author: author, + project: project + } do + activity_content = %{ + "stem" => "1", + "authoring" => %{ + "parts" => [ + %{ + "id" => "1", + "gradingApproach" => "manual", + "responses" => [], + "scoringStrategy" => "best", + "evaluationStrategy" => "regex" + } + ] + } + } + + {:ok, {first_activity_revision, _}} = + ActivityEditor.create( + project.slug, + "oli_short_answer", + author, + activity_content, + [], + "embedded", + "First embedded activity" + ) + + {:ok, {second_activity_revision, _}} = + ActivityEditor.create( + project.slug, + "oli_short_answer", + author, + activity_content, + [], + "embedded", + "Second embedded activity" + ) + + page = %{ + objectives: %{"attached" => []}, + children: [], + content: %{ + "model" => [ + %{ + "id" => "first-reference", + "type" => "activity-reference", + "children" => [], + "activity_id" => first_activity_revision.resource_id + }, + %{ + "id" => "second-reference", + "type" => "activity-reference", + "children" => [], + "activity_id" => second_activity_revision.resource_id + } + ] + }, + title: "Two Activity Page", + graded: true, + resource_type_id: Oli.Resources.ResourceType.id_for_page() + } + + root_container = AuthoringResolver.root_container(project.slug) + {:ok, page_revision} = ContainerEditor.add_new(root_container, page, author, project) + + {:ok, duplicated_page_revision} = + ContainerEditor.duplicate_page(root_container, page_revision.id, author, project) + + [first_reference, second_reference] = duplicated_page_revision.content["model"] + + assert Enum.map(duplicated_page_revision.content["model"], & &1["id"]) == [ + "first-reference", + "second-reference" + ] + + refute first_reference["activity_id"] == first_activity_revision.resource_id + refute second_reference["activity_id"] == second_activity_revision.resource_id + end end describe "adaptive page duplication gating" do