Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/oli/authoring/editing/container_editor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
56 changes: 21 additions & 35 deletions lib/oli_web/live/resources/pages_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
56 changes: 21 additions & 35 deletions lib/oli_web/live/workspaces/course_author/pages_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
82 changes: 82 additions & 0 deletions test/oli/editing/container_editor_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading