diff --git a/packages/frontend/app/components/reports/subject-header.js b/packages/frontend/app/components/reports/subject-header.js
index 16dd02d76c..56929ad60c 100644
--- a/packages/frontend/app/components/reports/subject-header.js
+++ b/packages/frontend/app/components/reports/subject-header.js
@@ -4,7 +4,7 @@ import { action } from '@ember/object';
import { service } from '@ember/service';
import PapaParse from 'papaparse';
import { dropTask, timeout, waitForProperty } from 'ember-concurrency';
-import createDownloadFile from 'frontend/utils/create-download-file';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
import { validatable, Length } from 'ilios-common/decorators/validation';
import { TrackedAsyncData } from 'ember-async-data';
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs
index ab8c132d82..2ec5355603 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.hbs
@@ -26,6 +26,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -78,4 +88,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js
index 17098949a4..78ac1743a9 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-instructor-session-type-graph.js
@@ -1,12 +1,14 @@
import Component from '@glimmer/component';
import { filter, map } from 'rsvp';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
-import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
import { action } from '@ember/object';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
+import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeInstructorSessionTypeGraph extends Component {
@service router;
@@ -135,4 +137,21 @@ export default class CourseVisualizeInstructorSessionTypeGraph extends Component
);
this.tooltipContent = htmlSafe(uniqueValues(mapBy(meta.sessions, 'title')).sort().join(', '));
});
+
+ downloadData = dropTask(async () => {
+ const data = await this.getData(this.args.course, this.args.user);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.sessionType')}`] = obj.meta.sessionType.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(
+ `ilios-course-${this.args.course.id}-instructor-${this.args.user.id}-session-types.csv`,
+ csv,
+ 'text/csv',
+ );
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs
index b9bad31545..4e542b8aa5 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.hbs
@@ -26,6 +26,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -78,4 +88,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js
index 7bf9e2486f..0571bd08ac 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-instructor-term-graph.js
@@ -2,12 +2,14 @@ import Component from '@glimmer/component';
import { filter, map } from 'rsvp';
import { isEmpty } from '@ember/utils';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
-import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
import { action } from '@ember/object';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
+import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeInstructorTermGraph extends Component {
@service router;
@@ -156,4 +158,21 @@ export default class CourseVisualizeInstructorTermGraph extends Component {
);
this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
+
+ downloadData = dropTask(async () => {
+ const data = await this.getData(this.args.course, this.args.user);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.term')}`] = obj.meta.term.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(
+ `ilios-course-${this.args.course.id}-instructor-${this.args.user.id}-vocabulary-terms.csv`,
+ csv,
+ 'text/csv',
+ );
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs b/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs
index c7ea34878a..5cd8f7c389 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs
@@ -27,6 +27,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -83,4 +93,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-instructors-graph.js b/packages/ilios-common/addon/components/course/visualize-instructors-graph.js
index e85ef88966..85fde461b9 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.js
@@ -1,13 +1,15 @@
import Component from '@glimmer/component';
import { isEmpty } from '@ember/utils';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { map } from 'rsvp';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { cleanQuery } from 'ilios-common/utils/query-utils';
import { TrackedAsyncData } from 'ember-async-data';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeInstructorsGraph extends Component {
@@ -155,4 +157,17 @@ export default class CourseVisualizeInstructorsGraph extends Component {
this.router.transitionTo('course-visualize-instructor', this.args.course.id, obj.meta.user.id);
}
+
+ downloadData = dropTask(async () => {
+ const data = await this.getData(this.args.course);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.instructor')}`] = obj.meta.user.fullName;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(`ilios-course-${this.args.course.id}-instructors.csv`, csv, 'text/csv');
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs b/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs
index 10795f70f8..b62480d64d 100644
--- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs
@@ -53,6 +53,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -126,4 +136,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-objectives-graph.js b/packages/ilios-common/addon/components/course/visualize-objectives-graph.js
index 2136bc8ef5..8a7b6d7690 100644
--- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.js
@@ -4,9 +4,11 @@ import { action } from '@ember/object';
import { service } from '@ember/service';
import { htmlSafe } from '@ember/template';
import { filter, map } from 'rsvp';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { TrackedAsyncData } from 'ember-async-data';
+import PapaParse from 'papaparse';
import { mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
export default class CourseVisualizeObjectivesGraph extends Component {
@service router;
@@ -38,6 +40,7 @@ export default class CourseVisualizeObjectivesGraph extends Component {
get hasData() {
return this.data.length;
}
+
get sortedAscending() {
return this.sortBy.search(/desc/) === -1;
}
@@ -191,4 +194,23 @@ export default class CourseVisualizeObjectivesGraph extends Component {
);
this.tooltipContent = htmlSafe(mapBy(meta.sessionObjectives, 'sessionTitle').sort().join(', '));
});
+
+ downloadData = dropTask(async () => {
+ const sessions = await this.args.course.sessions;
+ const data = await this.getDataObjects(sessions);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[this.intl.t('general.percentage')] = obj.percentage;
+ rhett[this.intl.t('general.courseObjective')] = obj.meta.courseObjective.title;
+ rhett[this.intl.t('general.competencies')] = obj.meta.competencies;
+ rhett[this.intl.t('general.sessions')] = mapBy(
+ sortBy(mapBy(obj.meta.sessionObjectives, 'session'), 'title'),
+ 'title',
+ ).join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(`ilios-course-${this.args.course.id}-objectives.csv`, csv, 'text/csv');
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs b/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs
index 5b1610c25a..3a6b81e0d7 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-session-type-graph.hbs
@@ -26,6 +26,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -78,4 +88,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-session-type-graph.js b/packages/ilios-common/addon/components/course/visualize-session-type-graph.js
index 5d952ef73c..69c7d132c8 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-type-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-session-type-graph.js
@@ -2,12 +2,14 @@ import Component from '@glimmer/component';
import { map } from 'rsvp';
import { isEmpty } from '@ember/utils';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
-import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
import { action } from '@ember/object';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
+import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeSessionTypeGraph extends Component {
@service router;
@@ -149,4 +151,22 @@ export default class CourseVisualizeSessionTypeGraph extends Component {
this.tooltipTitle = title;
this.tooltipContent = content;
});
+
+ downloadData = dropTask(async () => {
+ const data = await this.getDataObjects(this.args.course, this.args.sessionType);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.vocabulary')} - ${this.intl.t('general.term')}`] =
+ `${obj.meta.vocabulary.title} - ${obj.meta.term.title}`;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(
+ `ilios-course-${this.args.course.id}-session-type-${this.args.sessionType.id}-vocabulary-terms.csv`,
+ csv,
+ 'text/csv',
+ );
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs b/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs
index 5b23278aaf..b47d7f4a94 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-session-types-graph.hbs
@@ -27,6 +27,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -83,4 +93,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-session-types-graph.js b/packages/ilios-common/addon/components/course/visualize-session-types-graph.js
index aaa30cc606..5d43453270 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-types-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-session-types-graph.js
@@ -1,13 +1,15 @@
import Component from '@glimmer/component';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { cleanQuery } from 'ilios-common/utils/query-utils';
import { map } from 'rsvp';
import { TrackedAsyncData } from 'ember-async-data';
+import PapaParse from 'papaparse';
+import { cleanQuery } from 'ilios-common/utils/query-utils';
import { findById, mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
export default class CourseVisualizeSessionTypesGraph extends Component {
@service router;
@@ -156,4 +158,17 @@ export default class CourseVisualizeSessionTypesGraph extends Component {
obj.meta.sessionType.id,
);
}
+
+ downloadData = dropTask(async () => {
+ const data = await this.getData(this.args.course);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[this.intl.t('general.sessionType')] = obj.meta.sessionType.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(`ilios-course-${this.args.course.id}-session-types.csv`, csv, 'text/csv');
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-term-graph.hbs b/packages/ilios-common/addon/components/course/visualize-term-graph.hbs
index 51fd37cd4c..437164ea2e 100644
--- a/packages/ilios-common/addon/components/course/visualize-term-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-term-graph.hbs
@@ -26,6 +26,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -78,4 +88,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-term-graph.js b/packages/ilios-common/addon/components/course/visualize-term-graph.js
index 9e63e4169f..d245d17660 100644
--- a/packages/ilios-common/addon/components/course/visualize-term-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-term-graph.js
@@ -1,12 +1,14 @@
import Component from '@glimmer/component';
import { map } from 'rsvp';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
-import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
import { action } from '@ember/object';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
+import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeTermGraph extends Component {
@service router;
@@ -121,4 +123,21 @@ export default class CourseVisualizeTermGraph extends Component {
);
this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
+
+ downloadData = dropTask(async () => {
+ const data = await this.getDataObjects(this.args.course, this.args.term);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.sessionType')}`] = obj.meta.sessionType.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(
+ `ilios-course-${this.args.course.id}-vocabulary-term-${this.args.term.id}-session-types`,
+ csv,
+ 'text/csv',
+ );
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs
index 166414ed68..d58d9a001f 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs
@@ -27,6 +27,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -83,4 +93,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js
index 677f69ffdc..e84cb3fb28 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js
@@ -1,11 +1,13 @@
import Component from '@glimmer/component';
import { all, map } from 'rsvp';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { TrackedAsyncData } from 'ember-async-data';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
import { findById, mapBy, sortBy, uniqueById } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeVocabulariesGraph extends Component {
@@ -44,7 +46,6 @@ export default class CourseVisualizeVocabulariesGraph extends Component {
rhett.vocabulary = obj.meta.vocabulary;
rhett.vocabularyTitle = obj.meta.vocabulary.title;
rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
- rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
return rhett;
});
}
@@ -150,4 +151,17 @@ export default class CourseVisualizeVocabulariesGraph extends Component {
obj.meta.vocabulary.id,
);
}
+
+ downloadData = dropTask(async () => {
+ const data = await this.getDataObjects(this.args.course);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.vocabulary')}`] = obj.meta.vocabulary.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(`ilios-course-${this.args.course.id}-vocabularies.csv`, csv, 'text/csv');
+ });
}
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs
index 828daf18cd..f10ea7d568 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs
@@ -27,6 +27,16 @@
{{/if}}
{{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
@@ -83,4 +93,4 @@
{{else}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js
index 68719c65d4..e9e42bfc0d 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js
@@ -1,11 +1,13 @@
import Component from '@glimmer/component';
import { filter, map } from 'rsvp';
import { htmlSafe } from '@ember/template';
-import { restartableTask, timeout } from 'ember-concurrency';
+import { dropTask, restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { TrackedAsyncData } from 'ember-async-data';
+import PapaParse from 'papaparse';
+import createDownloadFile from 'ilios-common/utils/create-download-file';
import { findById, mapBy, sortBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeVocabularyGraph extends Component {
@@ -148,4 +150,21 @@ export default class CourseVisualizeVocabularyGraph extends Component {
}
this.router.transitionTo('course-visualize-term', this.args.course.id, obj.meta.term.id);
}
+
+ downloadData = dropTask(async () => {
+ const data = await this.getDataObjects(this.args.course, this.args.vocabulary.id);
+ const output = data.map((obj) => {
+ const rhett = {};
+ rhett[`${this.intl.t('general.term')}`] = obj.meta.term.title;
+ rhett[this.intl.t('general.sessions')] = mapBy(obj.meta.sessions, 'title').sort().join(', ');
+ rhett[this.intl.t('general.minutes')] = obj.data;
+ return rhett;
+ });
+ const csv = PapaParse.unparse(output);
+ createDownloadFile(
+ `ilios-course-${this.args.course.id}-vocabulary-${this.args.vocabulary.id}`,
+ csv,
+ 'text/csv',
+ );
+ });
}
diff --git a/packages/frontend/app/utils/create-download-file.js b/packages/ilios-common/addon/utils/create-download-file.js
similarity index 100%
rename from packages/frontend/app/utils/create-download-file.js
rename to packages/ilios-common/addon/utils/create-download-file.js
diff --git a/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss b/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss
index f3150802d2..6a7fef6e93 100644
--- a/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss
+++ b/packages/ilios-common/app/styles/ilios-common/mixins/data-visualization.scss
@@ -34,5 +34,6 @@
grid-template-columns: 1fr;
grid-gap: 10px;
margin-left: 0.8rem;
+ margin-right: 0.8rem;
}
}
diff --git a/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss b/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss
index a42d1f9f17..263a68d972 100644
--- a/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss
+++ b/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss
@@ -10,6 +10,10 @@
grid-column: -1/1;
padding-top: 2rem;
+ .table-actions {
+ text-align: right;
+ }
+
table {
@include t.ilios-table-structure;
@include t.ilios-table-colors;
diff --git a/packages/ilios-common/app/utils/create-download-file.js b/packages/ilios-common/app/utils/create-download-file.js
new file mode 100644
index 0000000000..c7bc7687db
--- /dev/null
+++ b/packages/ilios-common/app/utils/create-download-file.js
@@ -0,0 +1 @@
+export { default } from 'ilios-common/utils/create-download-file';
diff --git a/packages/ilios-common/package.json b/packages/ilios-common/package.json
index 0fac429227..09a8de844d 100644
--- a/packages/ilios-common/package.json
+++ b/packages/ilios-common/package.json
@@ -63,6 +63,7 @@
"luxon": ">= 3.5.0",
"mockdate": ">= 3.0.0",
"normalize.css": "^8.0.1",
+ "papaparse": "^5.4.1",
"query-string": ">= 9.1.0",
"scroll-into-view": ">= 1.16.0",
"striptags": ">= 3.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 81f605fcb7..4e02f5a4ef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -149,7 +149,7 @@ importers:
version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19)
ember-cli-dependency-checker:
specifier: ^3.3.2
- version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
+ version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
ember-cli-dependency-lint:
specifier: 2.0.1
version: 2.0.1
@@ -212,7 +212,7 @@ importers:
version: 9.0.0(ember-qunit@8.1.1(@ember/test-helpers@3.3.1(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(webpack@5.96.1))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(qunit@2.22.0))(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))(qunit@2.22.0)(webpack@5.96.1)
ember-focus-trap:
specifier: ^1.1.0
- version: 1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))
+ version: 1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))
ember-load-initializers:
specifier: ^2.1.2
version: 2.1.2(@babel/core@7.26.0)
@@ -434,7 +434,7 @@ importers:
version: 9.1.0(@ember/test-helpers@4.0.4(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)))(@glimmer/component@1.1.2(@babel/core@7.26.0))(@glimmer/tracking@1.1.2)(ember-modifier@4.2.0(@babel/core@7.26.0)(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)))(miragejs@0.1.48)(tracked-built-ins@3.3.0)(webpack@5.96.1)
ember-focus-trap:
specifier: ^1.0.0
- version: 1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))
+ version: 1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1))
ember-in-element-polyfill:
specifier: ^1.0.0
version: 1.0.1
@@ -489,6 +489,9 @@ importers:
normalize.css:
specifier: ^8.0.1
version: 8.0.1
+ papaparse:
+ specifier: ^5.4.1
+ version: 5.4.1
query-string:
specifier: '>= 9.1.0'
version: 9.1.1
@@ -570,7 +573,7 @@ importers:
version: 3.0.0
ember-cli-dependency-checker:
specifier: ^3.3.2
- version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
+ version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
ember-cli-inject-live-reload:
specifier: ^2.1.0
version: 2.1.0
@@ -759,7 +762,7 @@ importers:
version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19)
ember-cli-dependency-checker:
specifier: ^3.3.2
- version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
+ version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
ember-cli-dependency-lint:
specifier: 2.0.1
version: 2.0.1
@@ -990,7 +993,7 @@ importers:
version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19)
ember-cli-dependency-checker:
specifier: ^3.3.2
- version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
+ version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
ember-cli-dependency-lint:
specifier: 2.0.1
version: 2.0.1
@@ -1215,7 +1218,7 @@ importers:
version: 3.1.0(@embroider/compat@3.6.5(@embroider/core@3.4.19))(@embroider/core@3.4.19)
ember-cli-dependency-checker:
specifier: ^3.3.2
- version: 3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
+ version: 3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7))
ember-cli-htmlbars:
specifier: ^6.3.0
version: 6.3.0
@@ -5095,8 +5098,8 @@ packages:
'@embroider/core':
optional: true
- ember-cli-dependency-checker@3.3.3:
- resolution: {integrity: sha512-mvp+HrE0M5Zhc2oW8cqs8wdhtqq0CfQXAYzaIstOzHJJn/U01NZEGu3hz7J7zl/+jxZkyygylzcS57QqmPXMuQ==}
+ ember-cli-dependency-checker@3.3.2:
+ resolution: {integrity: sha512-PwkrW5oYsdPWwt+0Tojufmv/hxVETTjkrEdK7ANQB2VSnqpA5UcYubwpQM9ONuR2J8wyNDMwEHlqIrk/FYtBsQ==}
engines: {node: '>= 6'}
peerDependencies:
ember-cli: ^3.2.0 || >=4.0.0
@@ -5379,11 +5382,11 @@ packages:
miragejs:
optional: true
- ember-focus-trap@1.1.1:
- resolution: {integrity: sha512-5tOWu6eV1UoNZE+P9Gl9lJXNrENZVCoOXi52ePb7JOrOZ3ckOk1OkPsFwR4Jym9VJ7vZ6S3Z3D8BrkFa2aCpYw==}
+ ember-focus-trap@1.1.0:
+ resolution: {integrity: sha512-KxbCKpAJaBVZm+bW4tHPoBJAZThmxa6pI+WQusL+bj0RtAnGUNkWsVy6UBMZ5QqTQzf4EvGHkCVACVp5lbAWMQ==}
engines: {node: 12.* || >= 14}
peerDependencies:
- ember-source: '>= 4.0.0'
+ ember-source: ^4.0.0 || ^5.0.0
ember-functions-as-helper-polyfill@2.1.2:
resolution: {integrity: sha512-yvW6xykvZEIYzzwlrC/g9yu6LtLkkj5F+ho6U+BDxN1uREMgoMOZnji7sSILn5ITVpaJ055DPcO+utEFD7IZOA==}
@@ -6081,6 +6084,9 @@ packages:
resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
engines: {node: '>=18'}
+ find-yarn-workspace-root@1.2.1:
+ resolution: {integrity: sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==}
+
find-yarn-workspace-root@2.0.0:
resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==}
@@ -9005,8 +9011,8 @@ packages:
stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
- streamx@2.20.2:
- resolution: {integrity: sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==}
+ streamx@2.20.1:
+ resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==}
string-template@0.2.1:
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
@@ -9321,11 +9327,11 @@ packages:
tiny-lr@2.0.0:
resolution: {integrity: sha512-f6nh0VMRvhGx4KCeK1lQ/jaL0Zdb5WdR+Jk8q9OSUQnaSDxAEGH1fgqLZ+cMl5EW3F2MGnCsalBO1IsnnogW1Q==}
- tldts-core@6.1.61:
- resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==}
+ tldts-core@6.1.60:
+ resolution: {integrity: sha512-XHjoxak8SFQnHnmYHb3PcnW5TZ+9ErLZemZei3azuIRhQLw4IExsVbL3VZJdHcLeNaXq6NqawgpDPpjBOg4B5g==}
- tldts@6.1.61:
- resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==}
+ tldts@6.1.60:
+ resolution: {integrity: sha512-TYVHm7G9NCnhgqOsFalbX6MG1Po5F4efF+tLfoeiOGQq48Oqgwcgz8upY2R1BHWa4aDrj28RYx0dkYJ63qCFMg==}
hasBin: true
tmp@0.0.28:
@@ -13724,7 +13730,7 @@ snapshots:
bare-stream@2.3.2:
dependencies:
- streamx: 2.20.2
+ streamx: 2.20.1
optional: true
base64-js@1.5.1: {}
@@ -15491,14 +15497,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- ember-cli-dependency-checker@3.3.3(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)):
+ ember-cli-dependency-checker@3.3.2(ember-cli@5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)):
dependencies:
chalk: 2.4.2
ember-cli: 5.11.0(babel-core@6.26.3)(ejs@3.1.10)(handlebars@4.7.8)(underscore@1.13.7)
- find-yarn-workspace-root: 2.0.0
+ find-yarn-workspace-root: 1.2.1
is-git-url: 1.0.0
resolve: 1.22.8
semver: 5.7.2
+ transitivePeerDependencies:
+ - supports-color
ember-cli-dependency-lint@2.0.1:
dependencies:
@@ -16247,7 +16255,7 @@ snapshots:
- supports-color
- webpack
- ember-focus-trap@1.1.1(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)):
+ ember-focus-trap@1.1.0(ember-source@5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)):
dependencies:
'@embroider/addon-shim': 1.9.0
ember-source: 5.11.1(@glimmer/component@1.1.2(@babel/core@7.26.0))(rsvp@4.8.5)(webpack@5.96.1)
@@ -17416,6 +17424,13 @@ snapshots:
path-exists: 5.0.0
unicorn-magic: 0.1.0
+ find-yarn-workspace-root@1.2.1:
+ dependencies:
+ fs-extra: 4.0.3
+ micromatch: 3.1.10
+ transitivePeerDependencies:
+ - supports-color
+
find-yarn-workspace-root@2.0.0:
dependencies:
micromatch: 4.0.8
@@ -20635,7 +20650,7 @@ snapshots:
stream-shift@1.0.3: {}
- streamx@2.20.2:
+ streamx@2.20.1:
dependencies:
fast-fifo: 1.3.2
queue-tick: 1.0.1
@@ -20933,7 +20948,7 @@ snapshots:
dependencies:
b4a: 1.6.7
fast-fifo: 1.3.2
- streamx: 2.20.2
+ streamx: 2.20.1
tar@6.2.1:
dependencies:
@@ -21188,11 +21203,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- tldts-core@6.1.61: {}
+ tldts-core@6.1.60: {}
- tldts@6.1.61:
+ tldts@6.1.60:
dependencies:
- tldts-core: 6.1.61
+ tldts-core: 6.1.60
tmp@0.0.28:
dependencies:
@@ -21247,7 +21262,7 @@ snapshots:
tough-cookie@5.0.0:
dependencies:
- tldts: 6.1.61
+ tldts: 6.1.60
tr46@0.0.3: {}