Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curriculum Report #8268

Open
wants to merge 59 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d9b18d8
Make Space for Course Report
jrjohnson Dec 6, 2024
5e85996
Add Course Search and List
jrjohnson Dec 10, 2024
fd6fc5c
Run a courses report
jrjohnson Dec 13, 2024
b776689
Split Report Results
jrjohnson Dec 13, 2024
7c3fc5a
Report on Objectives
jrjohnson Dec 13, 2024
acac600
Downloadable Results
jrjohnson Dec 13, 2024
af8f1ab
Expand Course Report
jrjohnson Jan 29, 2025
91e01be
Choose Courses from a List
jrjohnson Jan 31, 2025
8a3fe40
Create Route Tree for Reports
jrjohnson Jan 31, 2025
66b536d
Move Course Report Content Into Curriculum Report
jrjohnson Feb 1, 2025
43f35be
Load School Data in the Model
jrjohnson Feb 1, 2025
159935e
Reorganize Curriculum Report
jrjohnson Feb 1, 2025
e0133f0
Style Action Buttons
jrjohnson Feb 2, 2025
51f08b3
Improve School Selection in Chooser
jrjohnson Feb 2, 2025
16ae0dc
Only Show Schools with Active Years
jrjohnson Feb 2, 2025
22b90f9
Display Selected Courses in Tag Cloud
jrjohnson Feb 2, 2025
d34c449
Execute Different Curriculum Reports
jrjohnson Feb 2, 2025
72cdb30
Improve Session Objective Report Summary
jrjohnson Feb 2, 2025
eea0688
Add Curriculum Learner Group Report
jrjohnson Feb 2, 2025
599dd79
Store Curriculum Report in URL
jrjohnson Feb 3, 2025
89875d6
Switch to Dropdown for School Picker
jrjohnson Feb 5, 2025
7d9814c
Load Fewer Courses
jrjohnson Feb 6, 2025
5a664fd
Remove Courses List
jrjohnson Feb 6, 2025
bed0a55
Show Different Message Before Reports are Selected
jrjohnson Feb 6, 2025
c28fc4a
Add Fancy Loader for Curriculum Reports
jrjohnson Feb 6, 2025
1c6a5d6
Don't Allow Picking a Report While Running
jrjohnson Feb 6, 2025
46bd3a6
Consolidate Curriculum Report Headers
jrjohnson Feb 7, 2025
aa47018
DRY Up Grouping Instructors
jrjohnson Feb 7, 2025
cd0780c
Fix Instructor Counts in Curriculum Reports
jrjohnson Feb 7, 2025
0fed18e
Add Loading Shimmer to Curriculum Reports
jrjohnson Feb 10, 2025
81861f7
Run Curriculum Report from Controller
jrjohnson Feb 12, 2025
62f0efb
Replace State with Courses Param
jrjohnson Feb 12, 2025
270abdd
Add Report Summary
jrjohnson Feb 12, 2025
83f3c4d
Add Select All for Course Chooser
jrjohnson Feb 12, 2025
7143e88
Add Chunk Array Helper
jrjohnson Feb 13, 2025
c78fcac
Add Loading Indicator for Learner Groups Report
jrjohnson Feb 13, 2025
b7b6c74
Always Provide Option to Close
jrjohnson Feb 13, 2025
6cab591
Add Tests for Course Chooser
jrjohnson Feb 13, 2025
ebce227
Replace Hand Coded GraphQL
jrjohnson Feb 13, 2025
6377bfd
Add Progress and Chunking to Session Objectives Report
jrjohnson Feb 13, 2025
df8543c
Add Label to Course Select All
jrjohnson Feb 14, 2025
afbbf7e
Extract buildSchoolFromData Helper
jrjohnson Feb 14, 2025
b10428a
Test Curriculum Report Header
jrjohnson Feb 14, 2025
42fffb0
Move Course Chooser
jrjohnson Feb 14, 2025
fb2a25d
Align Translations
jrjohnson Feb 14, 2025
643bee2
Test Session Objectives Report
jrjohnson Feb 19, 2025
9c2ff86
Add Test for Learner Groups Report
jrjohnson Feb 19, 2025
22b8c0e
DRY Up Report Tests
jrjohnson Feb 19, 2025
d4dc695
Remove Loading Test
jrjohnson Feb 19, 2025
93b2280
Fix List Source Order
jrjohnson Feb 20, 2025
7050377
Fix a11y Issues in Report Header
jrjohnson Feb 20, 2025
84383e6
Add Tests for Curriculum Report
jrjohnson Feb 20, 2025
dff156d
Handle zero correctly
jrjohnson Feb 20, 2025
e0bb6c8
Add Tests for Reports Switcher
jrjohnson Feb 21, 2025
c5e97de
Cleanup Existing Reports Test
jrjohnson Feb 21, 2025
8e1fc19
Add Some Acceptance Tests for Curriculum Reports
jrjohnson Feb 21, 2025
f390921
Track and Pass Finished State
jrjohnson Feb 26, 2025
a2d3f9e
Fix Typos
jrjohnson Feb 26, 2025
4b43498
Restore Auth Check
jrjohnson Feb 26, 2025
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
20 changes: 20 additions & 0 deletions packages/frontend/app/components/reports/curriculum.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="reports-curriculum" data-test-reports-curriculum ...attributes>
{{#if @showReportResults}}
<this.reportResultsComponent @courses={{this.selectedCourses}} @close={{@stop}} />
{{else}}
<Reports::Curriculum::Header
@countSelectedCourses={{@selectedCourseIds.length}}
@showReportResults={{@showReportResults}}
@selectedReportValue={{this.selectedReportValue}}
@changeSelectedReport={{this.changeSelectedReport}}
@runReport={{@run}}
@close={{@stop}}
/>
<Reports::Curriculum::ChooseCourse
@selectedCourseIds={{@selectedCourseIds}}
@schools={{@schools}}
@add={{this.pickCourse}}
@remove={{this.removeCourse}}
/>
{{/if}}
</div>
68 changes: 68 additions & 0 deletions packages/frontend/app/components/reports/curriculum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { ensureSafeComponent } from '@embroider/util';
import SessionObjectives from './curriculum/session-objectives';
import LearnerGroups from './curriculum/learner-groups';

export default class ReportsCurriculumComponent extends Component {
@service store;
@service graphql;
@service router;
@service intl;

@tracked searchResults = null;
@tracked reportResults = null;

get passedCourseIds() {
return this.args.selectedCourseIds?.map(Number) ?? [];
}

get selectedReportValue() {
return this.args.report ?? 'sessionObjectives';
}

@cached
get allCourses() {
return this.args.schools.reduce((all, school) => {
const courses = school.years.reduce((arr, year) => {
return [...arr, ...year.courses];
}, []);
return [...all, ...courses];
}, []);
}

get selectedCourses() {
return this.allCourses.filter((course) => this.passedCourseIds.includes(Number(course.id)));
}

get showCourseYears() {
const years = this.selectedCourses.map(({ year }) => year);
return years.some((year) => year !== years[0]);
}

get reportResultsComponent() {
switch (this.selectedReportValue) {
case 'sessionObjectives':
return ensureSafeComponent(SessionObjectives, this);
case 'learnerGroups':
return ensureSafeComponent(LearnerGroups, this);
}

return false;
}

pickCourse = (id) => {
this.args.setSelectedCourseIds([...this.passedCourseIds, Number(id)].sort());
};

removeCourse = (id) => {
this.args.stop();
this.args.setSelectedCourseIds(this.passedCourseIds.filter((i) => i !== Number(id)).sort());
};

changeSelectedReport = (value) => {
this.args.stop();
this.args.setReport(value);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<div class="reports-choose-course" data-test-reports-curriculum-choose-course>
<div class="schools" data-test-schools>
<FaIcon @icon="building-columns" />
{{#if (gt this.filteredSchools.length 1)}}
<select
aria-label={{t "general.filterBySchool"}}
{{on "change" (pick "target.value" (set this "selectedSchoolId"))}}
>
{{#each (sort-by "title" this.filteredSchools) as |school|}}
<option value={{school.id}} selected={{eq school.id this.bestSelectedSchoolId}}>
{{school.title}}
</option>
{{/each}}
</select>
{{else}}
{{this.selectedSchool.title}}
{{/if}}
</div>
{{#each this.selectedSchool.years as |y|}}
<ul class="year {{if (eq y.year this.expandedYear) 'expanded' 'collapsed'}}" data-test-year>
<li>
<button
type="button"
aria-expanded={{if (eq y.year this.expandedYear) "true" "false"}}
{{on "click" (fn this.toggleYear y.year)}}
data-test-expand
>
{{y.year}}
<FaIcon @icon={{if (eq y.year this.expandedYear) "caret-down" "caret-right"}} />
</button>
{{#if (eq y.year this.expandedYear)}}
<ul class="courses" data-test-courses>
<li>
<label class="select-all">
<input
type="checkbox"
checked={{this.hasAllExpandedYearCourses}}
indeterminate={{this.hasSomeExpandedYearCourses}}
{{on "click" this.toggleAllExpandedYearCourseSelection}}
disabled={{eq y.courses.length 0}}
data-test-toggle-all
/>
{{t "general.selectAllOrNone"}}
</label>
</li>
{{#each (sort-by "title" y.courses) as |c|}}
<li data-test-course>
<label>
<input
type="checkbox"
checked={{includes c.id @selectedCourseIds}}
{{on "click" (fn (if (includes c.id @selectedCourseIds) @remove @add) c.id)}}
/>
{{c.title}}
{{#if c.externalId}}
({{c.externalId}})
{{/if}}
</label>
</li>
{{/each}}
</ul>
{{/if}}
</li>
</ul>
{{/each}}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
import currentAcademicYear from 'ilios-common/utils/current-academic-year';

export default class ReportsCurriculumChooseCourse extends Component {
@service iliosConfig;
@service currentUser;

@tracked selectedSchoolId = null;
@tracked expandedYear;

constructor() {
super(...arguments);
this.expandedYear = currentAcademicYear();
}

userModel = new TrackedAsyncData(this.currentUser.getModel());

@cached
get user() {
return this.userModel.isResolved ? this.userModel.value : null;
}

get primarySchool() {
return this.args.schools.find(({ id }) => id === this.user?.belongsTo('school').id());
}

crossesBoundaryConfig = new TrackedAsyncData(
this.iliosConfig.itemFromConfig('academicYearCrossesCalendarYearBoundaries'),
);

@cached
get academicYearCrossesCalendarYearBoundaries() {
return this.crossesBoundaryConfig.isResolved ? this.crossesBoundaryConfig.value : false;
}

get bestSelectedSchoolId() {
if (this.selectedSchoolId) {
return this.selectedSchoolId;
}
return this.primarySchool?.id;
}

get selectedSchool() {
return this.args.schools.find(({ id }) => id === this.bestSelectedSchoolId);
}

get filteredSchools() {
return this.args.schools.filter(({ years }) => years.length);
}

get expandedYearCourseIds() {
const year = this.selectedSchool?.years.find(({ year }) => year === this.expandedYear);
return year?.courses.map(({ id }) => id) ?? [];
}

get hasSomeExpandedYearCourses() {
return (
this.args.selectedCourseIds &&
!this.hasAllExpandedYearCourses &&
this.expandedYearCourseIds.some((id) => this.args.selectedCourseIds.includes(id))
);
}

get hasAllExpandedYearCourses() {
return (
this.args.selectedCourseIds &&
this.expandedYearCourseIds.every((id) => this.args.selectedCourseIds.includes(id))
);
}

toggleYear = (year) => {
if (this.expandedYear === year) {
this.expandedYear = null;
} else {
this.expandedYear = year;
}
};

toggleAllExpandedYearCourseSelection = () => {
if (this.hasAllExpandedYearCourses) {
this.expandedYearCourseIds.forEach((id) => this.args.remove(id));
} else {
this.expandedYearCourseIds.forEach((id) => this.args.add(id));
}
};
}
60 changes: 60 additions & 0 deletions packages/frontend/app/components/reports/curriculum/header.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<div class="reports-curriculum-header" data-test-reports-curriculum-header>
<div class="run">
<p data-test-run-summary>
{{#if @countSelectedCourses}}
{{#if @showReportResults}}
{{t "general.run"}}
{{this.selectedReport.label}}
{{else}}
<label data-test-report-selector>
{{t "general.run"}}
<select {{on "change" this.changeSelectedReport}}>
{{#each this.reportList as |report|}}
<option
value={{report.value}}
selected={{eq report.value this.selectedReport.value}}
>
{{report.label}}
</option>
{{/each}}
</select>
</label>
{{/if}}
{{this.selectedReport.summary}}
{{else}}
{{t "general.selectCoursesToRunReport"}}
{{/if}}
</p>
</div>
<div class="input-buttons">
{{#if @showReportResults}}
{{#if @loading}}
<button type="button" class="done text">
<FaIcon @icon="spinner" @spin={{true}} />
</button>
{{else}}
<button type="button" {{on "click" @download}} data-test-download>
{{#if @finished}}
<FaIcon @icon="check" />
{{else}}
<FaIcon @icon="download" />
{{/if}}
{{t "general.downloadResults"}}
</button>
{{/if}}
<button type="button" class="cancel text" {{on "click" @close}} data-test-close>
{{t "general.close"}}
</button>
{{else}}
<button
type="button"
class="done text"
{{on "click" @runReport}}
disabled={{not @countSelectedCourses}}
data-test-run
>
<FaIcon @icon="play" @title={{t "general.run"}} />
</button>
{{/if}}
</div>
</div>
33 changes: 33 additions & 0 deletions packages/frontend/app/components/reports/curriculum/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';

export default class ReportsCurriculumHeader extends Component {
@service intl;

get reportList() {
return [
{
value: 'sessionObjectives',
label: this.intl.t('general.sessionObjectives'),
summary: this.intl.t('general.sessionObjectivesReportSummary', {
courseCount: this.args.countSelectedCourses,
}),
},
{
value: 'learnerGroups',
label: this.intl.t('general.learnerGroups'),
summary: this.intl.t('general.learnerGroupsReportSummary', {
courseCount: this.args.countSelectedCourses,
}),
},
];
}

get selectedReport() {
return this.reportList.find((r) => r.value === this.args.selectedReportValue);
}

changeSelectedReport = ({ target }) => {
this.args.changeSelectedReport(target.value);
};
}
Loading