Skip to content

Commit

Permalink
Merge branch 'Simon-Initiative:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
dtiwarATS committed Jun 3, 2024
2 parents bffba11 + dc3300a commit 1485f8b
Show file tree
Hide file tree
Showing 48 changed files with 1,721 additions and 430 deletions.
4 changes: 4 additions & 0 deletions assets/css/table.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ div:has(.instructor_dashboard_table) #header_paging {
@apply sticky left-0 dark:bg-neutral-800;
}

div:has(.instructor_dashboard_table) #footer_paging {
@apply sticky left-0 dark:bg-neutral-800;
}

div:has(.remix_materials_table) {
@apply overflow-x-hidden;
}
Expand Down
4 changes: 2 additions & 2 deletions assets/src/apps/scheduler/ScheduleGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ const LegendIconItem: React.FC<{ icon: string; text: string; color: string }> =
text,
color,
}) => (
<span className="inline-flex items-center mr-4 rounded-md bg-gray-100 py-1 px-3">
<span className="inline-flex items-center mr-4 rounded-md bg-gray-100 py-1 px-3 dark:bg-black">
<i className={`fa fa-${icon} mr-3 ${color}`} />
{text}
</span>
);

const LegendBarItem: React.FC = () => (
<span className="inline-flex items-center mr-4 rounded-md bg-gray-100 py-1 px-3">
<span className="inline-flex items-center mr-4 rounded-md bg-gray-100 py-1 px-3 dark:bg-black">
<span className="inline-block rounded bg-delivery-primary-300 dark:bg-delivery-primary-600 h-5 justify-between p-0.5 cursor-move w-10 mr-3" />
Suggested Range
</span>
Expand Down
63 changes: 63 additions & 0 deletions assets/src/hooks/countdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export const Countdown = {
/**
* Countdown Module
*
* This TypeScript module implements a countdown functionality for web elements with the `role="countdown"`
* attribute. It updates each element's display in real-time, decrementing the time every second from a
* specified starting point in "HH:MM:SS" format until it reaches zero.
*
* Use Case:
* - Suitable for real-time events like timers where a countdown is needed.
*
* Features:
* - Automatically finds and manages multiple countdowns on a single page.
* - Converts time format from "HH:MM:SS" to seconds for countdown operation, and updates the display accordingly.
* - Stops the countdown at zero and displays "00:00:00".
*/

mounted() {
this.handleCountdown();
},

updated() {
this.handleCountdown();
},

handleCountdown() {
const countdownElements: NodeListOf<HTMLElement> =
document.querySelectorAll('[role="countdown"]');
countdownElements.forEach((element) => {
const totalTimeInSeconds = this.timeToSeconds(element.innerText);
this.startCountdown(element, totalTimeInSeconds);
});
},

startCountdown(element: HTMLElement, totalTimeInSeconds: number) {
const timer = setInterval(() => {
totalTimeInSeconds -= 1;
element.innerText = this.secondsToTime(totalTimeInSeconds);

if (totalTimeInSeconds <= 0) {
clearInterval(timer);
element.innerText = '00:00:00'; // or trigger any final event/notification
}
}, 1000);
},

timeToSeconds(time: string): number {
const [hours, minutes, seconds] = time.split(':').map(Number);
return hours * 3600 + minutes * 60 + seconds;
},

secondsToTime(totalSeconds: number): string {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;

const paddedHours = hours.toString().padStart(2, '0');
const paddedMinutes = minutes.toString().padStart(2, '0');
const paddedSeconds = seconds.toString().padStart(2, '0');

return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
},
};
2 changes: 2 additions & 0 deletions assets/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BeforeUnloadListener } from './before_unload';
import { CheckboxListener } from './checkbox_listener';
import { ClickOutside } from './click_outside';
import { CopyListener } from './copy_listener';
import { Countdown } from './countdown';
import { CustomFocusWrap } from './custom_focus_wrap';
import { DateTimeLocalInputListener } from './datetimelocal_input_listener';
import { DragSource, DropTarget } from './dragdrop';
Expand Down Expand Up @@ -73,4 +74,5 @@ export const Hooks = {
PointMarkers,
AutoSelect,
CustomFocusWrap,
Countdown,
};
8 changes: 8 additions & 0 deletions assets/tailwind.theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,5 +315,13 @@ module.exports = {
hvxl: { raw: '(min-width: 1280px) and (min-height: 800px)' },
hv2xl: { raw: '(min-width: 1536px) and (min-height: 950px)' },
},
colors: {
checkpoint: {
DEFAULT: '#B27307',
dark: '#FF8F40',
},
practice: { DEFAULT: '#1D4ED8', dark: '#8CBCFF' },
exploration: { DEFAULT: '#A21CAF', dark: '#EC8CFF' },
},
},
};
2 changes: 1 addition & 1 deletion lib/oli/analytics/xapi/file_writer_uploader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ defmodule Oli.Analytics.XAPI.FileWriterUploader do

def upload(%StatementBundle{} = bundle) do
# write the bundle to a file in ./bundle_id.jsonl
File.write!("./test_bundles/#{bundle.bundle_id}.jsonl", bundle.body)
File.write("#{bundle.partition}/#{bundle.bundle_id}.jsonl", bundle.body)
end
end
4 changes: 2 additions & 2 deletions lib/oli/delivery/evaluation/rule.ex
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ defmodule Oli.Delivery.Evaluation.Rule do
defp is_range?(str), do: String.starts_with?(str, ["[", "("])

defp is_float?(str),
do: String.contains?(str, ".") or String.contains?(str, "e-")
do: String.contains?(str, ".") or String.contains?(str, "e") or String.contains?(str, "E")

defp parse_range(range_str) do
case Regex.run(
~r/([[(])\s*(-?[-+01234567890e.]+)\s*,\s*(-?[-+01234567890e.]+)\s*[\])]#?(\d+)?/,
~r/([[(])\s*(-?[-+01234567890eE.]+)\s*,\s*(-?[-+01234567890eE.]+)\s*[\])]#?(\d+)?/,
range_str
) do
[_, "[", lower, upper | maybe_precision] ->
Expand Down
143 changes: 128 additions & 15 deletions lib/oli/delivery/sections.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ defmodule Oli.Delivery.Sections do
alias Oli.Delivery.Sections.PostProcessing
alias Oli.Branding.CustomLabels
alias Oli.Delivery.Settings
alias Oli.Analytics.Summary.ResourceSummary

require Logger

Expand Down Expand Up @@ -1484,19 +1485,25 @@ defmodule Oli.Delivery.Sections do
[%{page_id: 1, containers: [%{id: 10, title: "Introduction", numbering_level: 1}]}]
"""

@spec get_ordered_containers_per_page(String.t()) :: [
@spec get_ordered_containers_per_page(String.t(), list(integer())) :: [
%{page_id: integer(), containers: list(map())}
]
def get_ordered_containers_per_page(section_slug) do
def get_ordered_containers_per_page(section_slug, page_ids \\ []) do
container_type_id = Oli.Resources.ResourceType.get_id_by_type("container")

pages_filter =
if Enum.empty?(page_ids),
do: true,
else: dynamic([_sr, _s, _spp, _pr, _rev, cp], cp.page_id in ^page_ids)

from(
[sr: sr, rev: rev, s: s] in DeliveryResolver.section_resource_revisions(section_slug),
join: cp in ContainedPage,
on: cp.container_id == rev.resource_id,
where:
rev.deleted == false and s.slug == ^section_slug and
rev.resource_type_id == ^container_type_id,
where: ^pages_filter,
group_by: [cp.page_id],
select: %{
page_id: cp.page_id,
Expand Down Expand Up @@ -2166,7 +2173,7 @@ defmodule Oli.Delivery.Sections do
@doc """
Returns the schedule for the current week and the next week for the given section and user.
"""
@spec get_schedule_for_current_and_next_week(%Oli.Delivery.Sections.Section{}, integer()) ::
@spec get_schedule_for_current_and_next_week(Section.t(), integer()) ::
any()
def get_schedule_for_current_and_next_week(section, current_user_id) do
current_week_number =
Expand Down Expand Up @@ -4040,8 +4047,8 @@ defmodule Oli.Delivery.Sections do
}
})
def get_resources_scheduled_dates_for_student(section_slug, student_id) do
from(sr in Oli.Delivery.Sections.SectionResource,
join: s in Oli.Delivery.Sections.Section,
from(sr in SectionResource,
join: s in Section,
on: sr.section_id == s.id and s.slug == ^section_slug,
left_join: se in Oli.Delivery.Settings.StudentException,
on:
Expand Down Expand Up @@ -4225,7 +4232,7 @@ defmodule Oli.Delivery.Sections do
## Returns:
- Returns a map with details of the last unfinished page if found, otherwise `nil`.
"""
@spec get_last_open_and_unfinished_page(Oli.Delivery.Sections.Section.t(), integer()) ::
@spec get_last_open_and_unfinished_page(Section.t(), integer()) ::
map() | nil
def get_last_open_and_unfinished_page(section, user_id) do
page_resource_type_id = Oli.Resources.ResourceType.get_id_by_type("page")
Expand All @@ -4252,33 +4259,139 @@ defmodule Oli.Delivery.Sections do
end

@doc """
Finds the nearest upcoming lesson in a specific section based on the current date.
Fetches the most recently accessed graded pages (completed or started) up to a given count for a specific user and section.
It returns a list of pages, each with details about the page and the last attempt on that page,
including state, update times, and scoring information. The result is ordered by the latest
update time of resource accesses.
## Parameters:
- `section`: The section struct containing details about the current section.
- `user_id`: The user ID for whom the data is fetched.
- `pages_count`: The maximum number of page records to return.
## Returns:
- A list of maps, each containing detailed information about a page, including metadata
like title, slug, and associated attempt details such as score and progress.
"""
@spec get_last_completed_or_started_assignments(Section.t(), integer(), integer()) :: [map()]
def get_last_completed_or_started_assignments(section, user_id, pages_count) do
page_resource_type_id = Oli.Resources.ResourceType.get_id_by_type("page")

# Subquery to fetch the last attempt for each page
last_attempt_query =
from(
r_att in ResourceAttempt,
join: ra in assoc(r_att, :resource_access),
where: ra.user_id == ^user_id,
select: %{
r_att
| row_number:
row_number()
|> over(partition_by: r_att.revision_id, order_by: [desc: r_att.updated_at])
}
)

base_query =
from(
[rev: rev, sr: sr] in Oli.Publishing.DeliveryResolver.section_resource_revisions(
section.slug
),
join: r_att in subquery(last_attempt_query),
on: r_att.revision_id == rev.id,
join: ra in assoc(r_att, :resource_access),
join: rs in ResourceSummary,
on: rs.resource_id == rev.resource_id and rs.user_id == ra.user_id,
where:
ra.section_id == ^section.id and ra.user_id == ^user_id and rev.graded and
rev.resource_type_id == ^page_resource_type_id and
rs.publication_id == -1 and rs.project_id == -1 and r_att.row_number == 1,
group_by: [rev.id, sr.numbering_index, sr.end_date, r_att.lifecycle_state],
select:
map(rev, [
:id,
:title,
:slug,
:duration_minutes,
:resource_id,
:graded,
:purpose,
:max_attempts
]),
select_merge: %{
numbering_index: sr.numbering_index,
end_date: sr.end_date,
latest_update: max(ra.updated_at),
attempts_count: count(r_att.id),
score: fragment("CAST(SUM(?) as float)", rs.num_correct),
out_of: fragment("CAST(SUM(?) as float)", rs.num_attempts),
progress: max(ra.progress),
last_attempt_state: r_att.lifecycle_state
}
)

Repo.all(
from(
result in subquery(base_query),
order_by: [desc: result.latest_update],
limit: ^pages_count
)
)
end

@doc """
Finds the nearest upcoming lessons in a specific section based on the current date and the lessons count.
## Parameters:
- `section`: The section struct containing details about the course section.
- `user_id`: The ID of the user.
- `lessons_count`: The number of upcoming lessons to retrieve.
## Returns:
- Returns a map with details of the upcoming lesson if found, otherwise `nil`.
- Returns a list of maps with details of the upcoming lessons.
"""
@spec get_nearest_upcoming_lesson(Oli.Delivery.Sections.Section.t()) :: map() | nil
def get_nearest_upcoming_lesson(section) do
today = Oli.DateTime.utc_now()
@spec get_nearest_upcoming_lessons(Section.t(), integer(), integer(), Keyword.t() | nil) ::
list(map())
def get_nearest_upcoming_lessons(section, user_id, lessons_count, opts \\ []) do
today =
Oli.Date.utc_today()
|> DateTime.new!(~T[00:00:00])

page_resource_type_id = Oli.Resources.ResourceType.get_id_by_type("page")

graded_filter =
if opts[:only_graded] do
dynamic([_sr, _s, _spp, _pr, rev, _ra], rev.graded)
else
true
end

from([rev: rev, sr: sr] in DeliveryResolver.section_resource_revisions(section.slug),
left_join: ra in ResourceAccess,
on: ra.resource_id == rev.resource_id and ra.user_id == ^user_id,
where:
rev.resource_type_id == ^page_resource_type_id and
coalesce(sr.start_date, sr.end_date) >= ^today,
coalesce(sr.start_date, sr.end_date) >= ^today and coalesce(ra.progress, 0) == 0,
order_by: [asc: coalesce(sr.start_date, sr.end_date), asc: sr.numbering_index],
limit: 1,
select: map(rev, [:id, :title, :slug, :duration_minutes, :resource_id, :graded, :purpose]),
limit: ^lessons_count,
select:
map(rev, [
:id,
:title,
:slug,
:duration_minutes,
:resource_id,
:graded,
:purpose,
:max_attempts
]),
select_merge: %{
numbering_index: sr.numbering_index,
start_date: sr.start_date,
end_date: sr.end_date
}
)
|> Repo.one()
|> where(^graded_filter)
|> Repo.all()
end

@doc """
Expand Down
1 change: 1 addition & 0 deletions lib/oli/resources/collaboration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ defmodule Oli.Resources.Collaboration do
|> join(:inner, [_s, p, _spp], u in User, on: p.user_id == u.id)
|> where(^section_join)
|> where([_s, p], p.status != :deleted)
|> where([_s, p], p.visibility != :private)
|> select([s, p, spp, pr, r, u], %{
id: p.id,
content: p.content,
Expand Down
9 changes: 5 additions & 4 deletions lib/oli_web/components/delivery/content/content.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
defmodule OliWeb.Components.Delivery.Content do
use OliWeb, :live_component

alias Phoenix.LiveView.JS

alias OliWeb.Common.{PagedTable, SearchInput}
alias OliWeb.Components.Delivery.ContentTableModel
alias OliWeb.Common.InstructorDashboardPagedTable
alias OliWeb.Common.SearchInput
alias OliWeb.Common.Params
alias OliWeb.Router.Helpers, as: Routes

alias Phoenix.LiveView.JS

@default_params %{
offset: 0,
limit: 20,
Expand Down Expand Up @@ -117,7 +118,7 @@ defmodule OliWeb.Components.Delivery.Content do
</.form>
</div>
<PagedTable.render
<InstructorDashboardPagedTable.render
table_model={@table_model}
total_count={@total_count}
offset={@params.offset}
Expand Down
Loading

0 comments on commit 1485f8b

Please sign in to comment.