diff --git a/packages/frontend/tests/acceptance/course-visualizations-instructor-test.js b/packages/frontend/tests/acceptance/course/visualizations-instructor-test.js
similarity index 79%
rename from packages/frontend/tests/acceptance/course-visualizations-instructor-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-instructor-test.js
index b3105c2f55..11be8c413e 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-instructor-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-instructor-test.js
@@ -13,7 +13,7 @@ module('Acceptance | course visualizations - instructor', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(21);
+ assert.expect(26);
const instructor = this.server.create('user');
const vocabulary1 = this.server.create('vocabulary');
const vocabulary2 = this.server.create('vocabulary');
@@ -78,24 +78,27 @@ module('Acceptance | course visualizations - instructor', function (hooks) {
// wait for charts to load
await waitFor('.loaded', { count: 2 });
await waitFor('svg .bars');
- await waitFor('svg .chart');
+ await waitFor('svg .slice');
await percySnapshot(assert);
assert.strictEqual(page.root.termsChart.chart.bars.length, 3);
assert.strictEqual(page.root.termsChart.chart.labels.length, 3);
+ assert.strictEqual(page.root.termsChart.chart.labels[0].text, 'Vocabulary 1 - term 0');
+ assert.strictEqual(page.root.termsChart.chart.labels[1].text, 'Vocabulary 1 - term 1');
+ assert.strictEqual(page.root.termsChart.chart.labels[2].text, 'Vocabulary 2 - term 2');
+ assert.strictEqual(page.root.sessionTypesChart.chart.slices.length, 2);
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels.length, 2);
+ assert.strictEqual(page.root.sessionTypesChart.chart.descriptions.length, 2);
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels[0].text, 'session type 1');
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels[1].text, 'session type 0');
+
assert.strictEqual(
- page.root.termsChart.chart.labels[0].text,
- 'Vocabulary 1 > term 0: 60 Minutes',
- );
- assert.strictEqual(
- page.root.termsChart.chart.labels[1].text,
- 'Vocabulary 1 > term 1: 30 Minutes',
+ page.root.sessionTypesChart.chart.descriptions[0].text,
+ 'session type 1 - 30 Minutes',
);
assert.strictEqual(
- page.root.termsChart.chart.labels[2].text,
- 'Vocabulary 2 > term 2: 30 Minutes',
+ page.root.sessionTypesChart.chart.descriptions[1].text,
+ 'session type 0 - 60 Minutes',
);
- assert.strictEqual(page.root.sessionTypesChart.chart.slices.length, 2);
- assert.strictEqual(page.root.sessionTypesChart.chart.slices[0].text, 'session type 0 66.7%');
- assert.strictEqual(page.root.sessionTypesChart.chart.slices[1].text, 'session type 1 33.3%');
+ assert.strictEqual(page.root.sessionTypesChart.dataTable.rows.length, 2);
});
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-instructors-test.js b/packages/frontend/tests/acceptance/course/visualizations-instructors-test.js
similarity index 87%
rename from packages/frontend/tests/acceptance/course-visualizations-instructors-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-instructors-test.js
index 954b8c9d41..3fe12387bb 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-instructors-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-instructors-test.js
@@ -65,7 +65,7 @@ module('Acceptance | course visualizations - instructors', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(12);
+ assert.expect(15);
await page.visit({ courseId: this.course.id });
assert.strictEqual(currentURL(), '/data/courses/1/instructors');
assert.strictEqual(page.root.title, 'course 0 2022');
@@ -80,15 +80,18 @@ module('Acceptance | course visualizations - instructors', function (hooks) {
await waitFor('svg .bars');
await percySnapshot(assert);
assert.strictEqual(page.root.instructorsChart.chart.bars.length, 2);
- assert.strictEqual(page.root.instructorsChart.chart.labels.length, 2);
assert.strictEqual(
- page.root.instructorsChart.chart.labels[0].text,
- '1 guy M. Mc1son: 75 Minutes',
+ page.root.instructorsChart.chart.bars[0].description,
+ '1 guy M. Mc1son - 75 Minutes',
);
assert.strictEqual(
- page.root.instructorsChart.chart.labels[1].text,
- '2 guy M. Mc2son: 90 Minutes',
+ page.root.instructorsChart.chart.bars[1].description,
+ '2 guy M. Mc2son - 90 Minutes',
);
+ assert.strictEqual(page.root.instructorsChart.chart.labels.length, 2);
+ assert.strictEqual(page.root.instructorsChart.chart.labels[0].text, '1 guy M. Mc1son');
+ assert.strictEqual(page.root.instructorsChart.chart.labels[1].text, '2 guy M. Mc2son');
+ assert.strictEqual(page.root.instructorsChart.dataTable.rows.length, 2);
});
test('clicking chart transitions user to instructor visualization', async function (assert) {
@@ -98,10 +101,7 @@ module('Acceptance | course visualizations - instructors', function (hooks) {
// wait for charts to load
await waitFor('.loaded');
await waitFor('svg .bars');
- assert.strictEqual(
- page.root.instructorsChart.chart.labels[0].text,
- '1 guy M. Mc1son: 75 Minutes',
- );
+ assert.strictEqual(page.root.instructorsChart.chart.labels[0].text, '1 guy M. Mc1son');
await page.root.instructorsChart.chart.bars[0].click();
assert.strictEqual(currentURL(), '/data/courses/1/instructors/2');
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-objectives-test.js b/packages/frontend/tests/acceptance/course/visualizations-objectives-test.js
similarity index 88%
rename from packages/frontend/tests/acceptance/course-visualizations-objectives-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-objectives-test.js
index 0782ce2f5a..c7bd2fa699 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-objectives-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-objectives-test.js
@@ -12,7 +12,7 @@ module('Acceptance | course visualizations - objectives', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(14);
+ assert.expect(17);
const school = this.server.create('school');
const course = this.server.create('course', { year: 2021, school });
const courseObjectives = this.server.createList('course-objective', 3, {
@@ -72,13 +72,22 @@ module('Acceptance | course visualizations - objectives', function (hooks) {
await waitFor('svg .chart');
await percySnapshot(assert);
assert.strictEqual(page.root.objectivesChart.chart.slices.length, 2);
- assert.strictEqual(page.root.objectivesChart.chart.slices[0].text, '77.8%');
- assert.strictEqual(page.root.objectivesChart.chart.slices[1].text, '22.2%');
+ assert.strictEqual(page.root.objectivesChart.chart.slices[0].label, '77.8%');
+ assert.strictEqual(
+ page.root.objectivesChart.chart.slices[0].description,
+ 'course objective 0 - 630 Minutes',
+ );
+ assert.strictEqual(page.root.objectivesChart.chart.slices[1].label, '22.2%');
+ assert.strictEqual(
+ page.root.objectivesChart.chart.slices[1].description,
+ 'course objective 1 - 180 Minutes',
+ );
assert.notOk(page.root.objectivesChart.unlinkedObjectives.isPresent);
assert.strictEqual(page.root.objectivesChart.untaughtObjectives.items.length, 1);
assert.strictEqual(
page.root.objectivesChart.untaughtObjectives.items[0].text,
'course objective 2',
);
+ assert.strictEqual(page.root.objectivesChart.dataTable.rows.length, 3);
});
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-session-type-test.js b/packages/frontend/tests/acceptance/course/visualizations-session-type-test.js
similarity index 83%
rename from packages/frontend/tests/acceptance/course-visualizations-session-type-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-session-type-test.js
index ed6b4e6a27..681492b6ba 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-session-type-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-session-type-test.js
@@ -13,7 +13,7 @@ module('Acceptance | course visualizations - session-type', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(16);
+ assert.expect(19);
const sessionType = this.server.create('session-type');
const vocabulary1 = this.server.create('vocabulary');
const vocabulary2 = this.server.create('vocabulary');
@@ -70,18 +70,21 @@ module('Acceptance | course visualizations - session-type', function (hooks) {
await percySnapshot(assert);
assert.strictEqual(page.root.title, 'course 0 2022');
assert.strictEqual(page.root.sessionTypeChart.chart.bars.length, 3);
- assert.strictEqual(page.root.sessionTypeChart.chart.labels.length, 3);
assert.strictEqual(
- page.root.sessionTypeChart.chart.labels[0].text,
- 'Vocabulary 1 - term 1: 30 Minutes',
+ page.root.sessionTypeChart.chart.bars[0].description,
+ 'Vocabulary 1 - term 1 - 30 Minutes',
);
assert.strictEqual(
- page.root.sessionTypeChart.chart.labels[1].text,
- 'Vocabulary 1 - term 0: 60 Minutes',
+ page.root.sessionTypeChart.chart.bars[1].description,
+ 'Vocabulary 1 - term 0 - 60 Minutes',
);
assert.strictEqual(
- page.root.sessionTypeChart.chart.labels[2].text,
- 'Vocabulary 2 - term 2: 30 Minutes',
+ page.root.sessionTypeChart.chart.bars[2].description,
+ 'Vocabulary 2 - term 2 - 30 Minutes',
);
+ assert.strictEqual(page.root.sessionTypeChart.chart.labels.length, 3);
+ assert.strictEqual(page.root.sessionTypeChart.chart.labels[0].text, 'Vocabulary 1 - term 1');
+ assert.strictEqual(page.root.sessionTypeChart.chart.labels[1].text, 'Vocabulary 1 - term 0');
+ assert.strictEqual(page.root.sessionTypeChart.chart.labels[2].text, 'Vocabulary 2 - term 2');
});
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-session-types-test.js b/packages/frontend/tests/acceptance/course/visualizations-session-types-test.js
similarity index 82%
rename from packages/frontend/tests/acceptance/course-visualizations-session-types-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-session-types-test.js
index 43e0b26e47..12943355be 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-session-types-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-session-types-test.js
@@ -13,7 +13,7 @@ module('Acceptance | course visualizations - session-types', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(14);
+ assert.expect(18);
const sessionType1 = this.server.create('session-type');
const sessionType2 = this.server.create('session-type');
const sessionType3 = this.server.create('session-type');
@@ -72,18 +72,22 @@ module('Acceptance | course visualizations - session-types', function (hooks) {
await percySnapshot(assert);
assert.strictEqual(page.root.title, 'course 0 2022');
assert.strictEqual(page.root.sessionTypesChart.chart.bars.length, 3);
- assert.strictEqual(page.root.sessionTypesChart.chart.labels.length, 3);
assert.strictEqual(
- page.root.sessionTypesChart.chart.labels[0].text,
- 'session type 1: 30 Minutes',
+ page.root.sessionTypesChart.chart.bars[0].description,
+ 'session type 1 - 30 Minutes',
);
assert.strictEqual(
- page.root.sessionTypesChart.chart.labels[1].text,
- 'session type 0: 60 Minutes',
+ page.root.sessionTypesChart.chart.bars[1].description,
+ 'session type 0 - 60 Minutes',
);
assert.strictEqual(
- page.root.sessionTypesChart.chart.labels[2].text,
- 'session type 2: 120 Minutes',
+ page.root.sessionTypesChart.chart.bars[2].description,
+ 'session type 2 - 120 Minutes',
);
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels.length, 3);
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels[0].text, 'session type 1');
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels[1].text, 'session type 0');
+ assert.strictEqual(page.root.sessionTypesChart.chart.labels[2].text, 'session type 2');
+ assert.strictEqual(page.root.sessionTypesChart.dataTable.rows.length, 3);
});
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-test.js b/packages/frontend/tests/acceptance/course/visualizations-test.js
similarity index 100%
rename from packages/frontend/tests/acceptance/course-visualizations-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-test.js
diff --git a/packages/frontend/tests/acceptance/course-visualizations-vocabularies-test.js b/packages/frontend/tests/acceptance/course/visualizations-vocabularies-test.js
similarity index 79%
rename from packages/frontend/tests/acceptance/course-visualizations-vocabularies-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-vocabularies-test.js
index 48c3460842..120b7de207 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-vocabularies-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-vocabularies-test.js
@@ -13,7 +13,7 @@ module('Acceptance | course visualizations - vocabularies', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(12);
+ assert.expect(16);
const sessionType = this.server.create('session-type');
const vocabulary1 = this.server.create('vocabulary');
const vocabulary2 = this.server.create('vocabulary');
@@ -67,10 +67,20 @@ module('Acceptance | course visualizations - vocabularies', function (hooks) {
assert.strictEqual(page.root.breadcrumb.crumbs[2].text, 'Vocabularies');
// wait for charts to load
await waitFor('.loaded');
- await waitFor('svg .chart');
+ await waitFor('svg .bars');
await percySnapshot(assert);
- assert.strictEqual(page.root.vocabulariesChart.chart.slices.length, 2);
- assert.strictEqual(page.root.vocabulariesChart.chart.slices[0].text, 'Vocabulary 1');
- assert.strictEqual(page.root.vocabulariesChart.chart.slices[1].text, 'Vocabulary 2');
+ assert.strictEqual(page.root.vocabulariesChart.chart.bars.length, 2);
+ assert.strictEqual(
+ page.root.vocabulariesChart.chart.bars[0].description,
+ 'Vocabulary 2 - 30 Minutes',
+ );
+ assert.strictEqual(
+ page.root.vocabulariesChart.chart.bars[1].description,
+ 'Vocabulary 1 - 90 Minutes',
+ );
+ assert.strictEqual(page.root.vocabulariesChart.chart.labels.length, 2);
+ assert.strictEqual(page.root.vocabulariesChart.chart.labels[0].text, 'Vocabulary 2');
+ assert.strictEqual(page.root.vocabulariesChart.chart.labels[1].text, 'Vocabulary 1');
+ assert.strictEqual(page.root.vocabulariesChart.dataTable.rows.length, 2);
});
});
diff --git a/packages/frontend/tests/acceptance/course-visualizations-vocabulary-test.js b/packages/frontend/tests/acceptance/course/visualizations-vocabulary-test.js
similarity index 89%
rename from packages/frontend/tests/acceptance/course-visualizations-vocabulary-test.js
rename to packages/frontend/tests/acceptance/course/visualizations-vocabulary-test.js
index 21051c0b3d..0314fb3adf 100644
--- a/packages/frontend/tests/acceptance/course-visualizations-vocabulary-test.js
+++ b/packages/frontend/tests/acceptance/course/visualizations-vocabulary-test.js
@@ -54,8 +54,7 @@ module('Acceptance | course visualizations - vocabulary', function (hooks) {
});
test('it renders', async function (assert) {
- assert.expect(17);
-
+ assert.expect(21);
await page.visit({ courseId: this.course.id, vocabularyId: this.vocabulary.id });
assert.strictEqual(currentURL(), '/data/courses/1/vocabularies/1');
assert.strictEqual(page.root.vocabularyTitle, 'Vocabulary 1');
@@ -74,10 +73,14 @@ module('Acceptance | course visualizations - vocabulary', function (hooks) {
await waitFor('svg .bars');
await percySnapshot(assert);
assert.strictEqual(page.root.termsChart.chart.bars.length, 3);
+ assert.strictEqual(page.root.termsChart.chart.bars[0].description, 'term 1 - 30 Minutes');
+ assert.strictEqual(page.root.termsChart.chart.bars[1].description, 'term 0 - 60 Minutes');
+ assert.strictEqual(page.root.termsChart.chart.bars[2].description, 'term 2 - 150 Minutes');
assert.strictEqual(page.root.termsChart.chart.labels.length, 3);
- assert.strictEqual(page.root.termsChart.chart.labels[0].text, 'term 1: 30 Minutes');
- assert.strictEqual(page.root.termsChart.chart.labels[1].text, 'term 0: 60 Minutes');
- assert.strictEqual(page.root.termsChart.chart.labels[2].text, 'term 2: 150 Minutes');
+ assert.strictEqual(page.root.termsChart.chart.labels[0].text, 'term 1');
+ assert.strictEqual(page.root.termsChart.chart.labels[1].text, 'term 0');
+ assert.strictEqual(page.root.termsChart.chart.labels[2].text, 'term 2');
+ assert.strictEqual(page.root.termsChart.dataTable.rows.length, 3);
});
test('clicking chart transitions user to term visualization', async function (assert) {
@@ -86,7 +89,7 @@ module('Acceptance | course visualizations - vocabulary', function (hooks) {
// wait for charts to load
await waitFor('.loaded');
await waitFor('svg .bars');
- assert.strictEqual(page.root.termsChart.chart.labels[0].text, 'term 1: 30 Minutes');
+ assert.strictEqual(page.root.termsChart.chart.labels[0].text, 'term 1');
await page.root.termsChart.chart.bars[0].click();
assert.strictEqual(currentURL(), '/data/courses/1/terms/2');
});
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-session-type-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-session-type-graph.js
index 45cc0f87c5..9ecd2fc6b9 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-session-type-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-session-type-graph.js
@@ -1,4 +1,4 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-instructor-session-type-graph]',
@@ -6,6 +6,39 @@ const definition = {
chart: {
scope: '.simple-chart',
slices: collection('svg .slice'),
+ labels: collection('.slice text'),
+ descriptions: collection('.slice desc'),
+ },
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ sessionType: {
+ scope: '[data-test-session-type]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ sessionType: text('[data-test-session-type]'),
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
},
};
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-term-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-term-graph.js
index f32aca173e..a54428542a 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-term-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructor-term-graph.js
@@ -1,13 +1,46 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-instructor-term-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- bars: collection('.bars rect'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
labels: collection('.bars text'),
},
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ vocabularyTerm: {
+ scope: '[data-test-vocabulary-term]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ vocabularyTerm: text('[data-test-vocabulary-term]'),
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
+ },
};
export default definition;
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructors-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructors-graph.js
index 9756c3a2f6..70ce0dcb8a 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructors-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-instructors-graph.js
@@ -1,13 +1,48 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-instructors-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- bars: collection('.bars rect'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
labels: collection('.bars text'),
- slices: collection('svg .slice'),
+ },
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ instructor: {
+ scope: '[data-test-instructor]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ instructor: {
+ scope: '[data-test-instructor]',
+ url: attribute('href', 'a'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
},
};
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-objectives-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-objectives-graph.js
index b9faf66f8a..aed42e518e 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-objectives-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-objectives-graph.js
@@ -5,7 +5,10 @@ const definition = {
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- slices: collection('svg .slice'),
+ slices: collection('svg .slice', {
+ label: text('text'),
+ description: text('desc'),
+ }),
},
unlinkedObjectives: {
scope: '[data-test-with-hours]',
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-type-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-type-graph.js
index 34a30b5bb5..31f6b8a8b5 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-type-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-type-graph.js
@@ -1,13 +1,46 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-session-type-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- bars: collection('.bars rect'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
labels: collection('.bars text'),
},
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ vocabularyTerm: {
+ scope: '[data-test-vocabulary-term]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ vocabularyTerm: text('[data-test-vocabulary-term]'),
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
+ },
};
export default definition;
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-types-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-types-graph.js
index 976103feab..fbf83f345d 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-types-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-session-types-graph.js
@@ -1,13 +1,48 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-session-types-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- bars: collection('.bars rect'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
labels: collection('.bars text'),
- slices: collection('svg .slice'),
+ },
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ sessionType: {
+ scope: '[data-test-session-type]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ sessionType: {
+ scope: '[data-test-session-type]',
+ url: attribute('href', 'a'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
},
};
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-term-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-term-graph.js
new file mode 100644
index 0000000000..21702acd81
--- /dev/null
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-term-graph.js
@@ -0,0 +1,47 @@
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
+
+const definition = {
+ scope: '[data-test-course-visualize-term-graph]',
+ isIcon: notHasClass('no-icon'),
+ chart: {
+ scope: '.simple-chart',
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
+ labels: collection('.bars text'),
+ },
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ sessionType: {
+ scope: '[data-test-session-type]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ sessionType: text('[data-test-session-type]'),
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
+ },
+};
+
+export default definition;
+export const component = create(definition);
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabularies-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabularies-graph.js
index d6498f2f17..7c6509d2ab 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabularies-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabularies-graph.js
@@ -1,11 +1,48 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-vocabularies-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- slices: collection('svg .slice'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
+ labels: collection('.bars text'),
+ },
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ vocabulary: {
+ scope: '[data-test-vocabulary]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ vocabulary: {
+ scope: '[data-test-vocabulary]',
+ url: attribute('href', 'a'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
},
};
diff --git a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabulary-graph.js b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabulary-graph.js
index 4a58a55a36..e484e2bc26 100644
--- a/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabulary-graph.js
+++ b/packages/ilios-common/addon-test-support/ilios-common/page-objects/components/course/visualize-vocabulary-graph.js
@@ -1,13 +1,49 @@
-import { collection, create, notHasClass } from 'ember-cli-page-object';
+import { attribute, clickable, collection, create, notHasClass, text } from 'ember-cli-page-object';
const definition = {
scope: '[data-test-course-visualize-vocabulary-graph]',
isIcon: notHasClass('no-icon'),
chart: {
scope: '.simple-chart',
- bars: collection('.bars rect'),
+ bars: collection('.bars rect', {
+ description: text('desc'),
+ }),
labels: collection('.bars text'),
},
+ noData: {
+ scope: '[data-test-no-data]',
+ },
+ dataTable: {
+ scope: '[data-test-data-table]',
+ header: {
+ scope: 'thead',
+ term: {
+ scope: '[data-test-term]',
+ toggle: clickable('button'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ toggle: clickable('button'),
+ },
+ minutes: {
+ scope: '[data-test-minutes]',
+ toggle: clickable('button'),
+ },
+ },
+ rows: collection('tbody tr', {
+ term: {
+ scope: '[data-test-term]',
+ url: attribute('href', 'a'),
+ },
+ sessions: {
+ scope: '[data-test-sessions]',
+ links: collection('a', {
+ url: attribute('href'),
+ }),
+ },
+ minutes: text('[data-test-minutes]'),
+ }),
+ },
};
export default definition;
diff --git a/packages/ilios-common/addon/components/course/visualizations.hbs b/packages/ilios-common/addon/components/course/visualizations.hbs
index 3c09c142d2..868378e5c9 100644
--- a/packages/ilios-common/addon/components/course/visualizations.hbs
+++ b/packages/ilios-common/addon/components/course/visualizations.hbs
@@ -44,7 +44,6 @@
@@ -53,7 +52,7 @@
{{t "general.vocabularies"}}
-
+
@@ -64,7 +63,6 @@
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 2020d604a4..ab8c132d82 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
@@ -3,19 +3,79 @@
data-test-course-visualize-instructor-session-type-graph
...attributes
>
- {{#if (or @isIcon this.data.length)}}
-
- {{#if this.tooltipContent}}
-
- {{this.tooltipContent}}
-
- {{/if}}
-
+ {{#if this.isLoaded}}
+ {{#if (or @isIcon this.hasChartData)}}
+
+ {{#if this.tooltipContent}}
+
+ {{this.tooltipContent}}
+
+ {{/if}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsInstructorNoData" instructor=@user.fullName}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.sessionType"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+ {{row.sessionType}} |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 a696afd29e..3ac7086b04 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,47 +1,77 @@
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 { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { action } from '@ember/object';
export default class CourseVisualizeInstructorSessionTypeGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getData(this.args.course, this.args.user));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
}
- @use loadedData = new AsyncProcess(() => [this.getData.bind(this), this.sessions]);
+ get hasData() {
+ return this.data.length;
+ }
- get data() {
- if (!this.loadedData) {
- return [];
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
+
+ get hasChartData() {
+ return this.chartData.length;
+ }
+
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.sessionType = obj.meta.sessionType.title;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
+ }
+
+ get isLoaded() {
+ return this.outputData.isResolved;
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
}
- return this.loadedData;
+ this.sortBy = prop;
}
- async getData(sessions) {
- if (!sessions) {
+ async getData(course, user) {
+ const sessions = await course.sessions;
+ if (!sessions.length) {
return [];
}
- const sessionsWithUser = await filter(sessions.slice(), async (session) => {
+ const sessionsWithUser = await filter(sessions, async (session) => {
const allInstructors = await session.getAllOfferingInstructors();
- return mapBy(allInstructors, 'id').includes(this.args.user.id);
+ return mapBy(allInstructors, 'id').includes(user.id);
});
const sessionsWithSessionType = await map(sessionsWithUser.slice(), async (session) => {
@@ -55,55 +85,54 @@ export default class CourseVisualizeInstructorSessionTypeGraph extends Component
const dataMap = await map(sessionsWithSessionType, async ({ session, sessionType }) => {
const minutes = await session.getTotalSumDurationByInstructor(this.args.user);
return {
- sessionTitle: session.title,
- sessionTypeTitle: sessionType.title,
+ session,
+ sessionType,
minutes,
};
});
- const sessionTypeData = dataMap.reduce((set, obj) => {
- const name = obj.sessionTypeTitle;
- let existing = findBy(set, 'label', name);
- if (!existing) {
- existing = {
- data: 0,
- label: name,
- meta: {
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(sessionTypeData, 'data').reduce(
- (total, minutes) => total + minutes,
- 0,
- );
+ return dataMap
+ .reduce((set, obj) => {
+ const id = obj.sessionType.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: obj.sessionType.title,
+ meta: {
+ sessions: [],
+ sessionType: obj.sessionType,
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += obj.minutes;
+ existing.meta.sessions.push(obj.session);
- return sessionTypeData.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.label} ${percent}%`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
- return obj;
- });
+ return set;
+ }, [])
+ .map((obj) => {
+ obj.description = `${obj.meta.sessionType.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
+ return obj;
+ })
+ .sort((first, second) => {
+ return first.data - second.data;
+ });
}
donutHover = restartableTask(async (obj) => {
await timeout(100);
- if (this.args.isIcon || isEmpty(obj) || obj.empty) {
+ if (this.args.isIcon || !obj || obj.empty) {
this.tooltipTitle = null;
this.tooltipContent = null;
return;
}
- const { label, data, meta } = obj;
-
- this.tooltipTitle = htmlSafe(`${label} ${data} ${this.intl.t('general.minutes')}`);
- this.tooltipContent = uniqueValues(meta.sessions).sort().join(', ');
+ const { data, meta } = obj;
+ this.tooltipTitle = htmlSafe(
+ `${meta.sessionType.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(uniqueValues(mapBy(meta.sessions, 'title')).sort().join(', '));
});
}
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 e793e14b2b..b9bad31545 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
@@ -1,21 +1,81 @@
- {{#if (or @isIcon this.data.length)}}
-
- {{#if this.tooltipContent}}
-
- {{this.tooltipContent}}
-
- {{/if}}
-
+ {{#if this.isLoaded}}
+ {{#if (or @isIcon this.hasChartData)}}
+
+ {{#if this.tooltipContent}}
+
+ {{this.tooltipContent}}
+
+ {{/if}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsInstructorNoData" instructor=@user.fullName}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.term"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+ {{row.vocabularyTerm}} |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 39bd41c478..7a8e779d11 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
@@ -5,51 +5,82 @@ import { htmlSafe } from '@ember/template';
import { restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy } from 'ilios-common/utils/array-helpers';
+import { action } from '@ember/object';
export default class CourseVisualizeInstructorTermGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getData(this.args.course, this.args.user));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
}
- @use loadedData = new AsyncProcess(() => [this.getData.bind(this), this.sessions]);
+ get hasData() {
+ return this.data.length;
+ }
- get data() {
- if (!this.loadedData) {
- return [];
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
+
+ get hasChartData() {
+ return this.chartData.length;
+ }
+
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.vocabularyTerm = `${obj.meta.vocabulary.title} - ${obj.meta.term.title}`;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
+ }
+
+ get isLoaded() {
+ return this.outputData.isResolved;
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
}
- return this.loadedData;
+ this.sortBy = prop;
}
- async getData(sessions) {
- if (!sessions) {
+ async getData(course, user) {
+ const sessions = await course.sessions;
+ if (!sessions.length) {
return [];
}
-
- const sessionsWithUser = await filter(sessions.slice(), async (session) => {
+ const sessionsWithUser = await filter(sessions, async (session) => {
const allInstructors = await session.getAllOfferingInstructors();
- return mapBy(allInstructors, 'id').includes(this.args.user.id);
+ return mapBy(allInstructors, 'id').includes(user.id);
});
const sessionsWithTerms = await map(sessionsWithUser, async (session) => {
- const terms = await map((await session.terms).slice(), async (term) => {
+ const sessionTerms = await session.terms;
+ const terms = await map(sessionTerms, async (term) => {
const vocabulary = await term.vocabulary;
return {
- termTitle: term.title,
- vocabularyTitle: vocabulary.title,
+ term,
+ vocabulary,
};
});
@@ -59,59 +90,52 @@ export default class CourseVisualizeInstructorTermGraph extends Component {
};
});
- const totalMinutes = (
- await map(sessionsWithTerms, async ({ session }) => {
- return await session.getTotalSumOfferingsDurationByInstructor(this.args.user);
- })
- ).reduce((total, mins) => total + mins, 0);
-
const dataMap = await map(sessionsWithTerms, async ({ session, terms }) => {
const minutes = await session.getTotalSumDurationByInstructor(this.args.user);
- return terms.map(({ termTitle, vocabularyTitle }) => {
+ return terms.map(({ term, vocabulary }) => {
return {
- sessionTitle: session.title,
- termTitle,
- vocabularyTitle,
+ session,
+ term,
+ vocabulary,
minutes,
};
});
});
- const flat = dataMap.reduce((flattened, arr) => {
- return [...flattened, ...arr];
- }, []);
-
- const sessionTermData = flat.reduce((set, obj) => {
- const name = `${obj.vocabularyTitle} > ${obj.termTitle}`;
- let existing = findBy(set, 'label', name);
- if (!existing) {
- existing = {
- data: 0,
- label: name,
- meta: {
- sessions: [],
- vocabularyTitle: obj.vocabularyTitle,
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
-
- return set;
- }, []);
-
- return sessionTermData
+ return dataMap
+ .reduce((flattened, arr) => {
+ return [...flattened, ...arr];
+ }, [])
+ .reduce((set, { term, session, vocabulary, minutes }) => {
+ const label = vocabulary.title + ' - ' + term.title;
+ const id = term.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label,
+ meta: {
+ term,
+ vocabulary,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+
+ return set;
+ }, [])
.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
- obj.label = `${obj.label}: ${obj.data} ${this.intl.t('general.minutes')}`;
+ (obj.description = `${obj.meta.vocabulary.title} - ${obj.meta.term.title} - ${obj.data} ${this.intl.t('general.minutes')}`),
+ delete obj.id;
return obj;
})
.sort((first, second) => {
return (
- first.meta.vocabularyTitle.localeCompare(second.meta.vocabularyTitle) ||
+ first.meta.vocabulary.title.localeCompare(second.meta.vocabulary.title) ||
second.data - first.data
);
});
@@ -124,9 +148,12 @@ export default class CourseVisualizeInstructorTermGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, meta } = obj;
- this.tooltipTitle = htmlSafe(label);
- this.tooltipContent = uniqueValues(meta.sessions).sort().join(', ');
+ const { data, meta } = obj;
+
+ this.tooltipTitle = htmlSafe(
+ `${meta.vocabulary.title} - ${meta.term.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
}
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor.hbs b/packages/ilios-common/addon/components/course/visualize-instructor.hbs
index 21477725d3..d43d20ef17 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructor.hbs
@@ -42,23 +42,25 @@
-
-
- {{t "general.terms"}}
-
-
-
-
-
- {{t "general.sessionTypes"}}
-
-
-
+
+
+ {{t "general.terms"}}
+
+
+
+
+
+ {{t "general.sessionTypes"}}
+
+
+
diff --git a/packages/ilios-common/addon/components/course/visualize-instructor.js b/packages/ilios-common/addon/components/course/visualize-instructor.js
index 0b63470c8b..7294f060cc 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructor.js
+++ b/packages/ilios-common/addon/components/course/visualize-instructor.js
@@ -1,10 +1,8 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { map, filter } from 'rsvp';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
import { cached } from '@glimmer/tracking';
-import AsyncProcess from 'ilios-common/classes/async-process';
import { mapBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeInstructorComponent extends Component {
@@ -25,31 +23,28 @@ export default class CourseVisualizeInstructorComponent extends Component {
}
get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ return this.sessionsData.isResolved ? this.sessionsData.value.slice() : [];
}
- @use minutes = new AsyncProcess(() => [this.getMinutes.bind(this), this.sessions]);
+ @cached
+ get minutesData() {
+ return new TrackedAsyncData(this.getMinutes(this.sessions));
+ }
+
+ get minutes() {
+ return this.minutesData.isResolved ? this.minutesData.value : [];
+ }
get totalInstructionalTime() {
- if (!this.minutes) {
- return 0;
- }
return mapBy(this.minutes, 'offeringMinutes').reduce((total, mins) => total + mins, 0);
}
get totalIlmTime() {
- if (!this.minutes) {
- return 0;
- }
return mapBy(this.minutes, 'ilmMinutes').reduce((total, mins) => total + mins, 0);
}
async getMinutes(sessions) {
- if (!sessions) {
- return [];
- }
-
- const sessionsWithUser = await filter(sessions.slice(), async (session) => {
+ const sessionsWithUser = await filter(sessions, async (session) => {
const instructors = await session.getAllInstructors();
return mapBy(instructors, 'id').includes(this.args.user.id);
});
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 d8e89ab29c..c7ea34878a 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.hbs
@@ -3,20 +3,84 @@
data-test-course-visualize-instructors-graph
...attributes
>
- {{#if (or @isIcon this.data.length)}}
-
- {{#if this.tooltipContent}}
-
- {{this.tooltipContent}}
-
- {{/if}}
-
+ {{#if this.isLoaded}}
+ {{#if (or @isIcon this.hasChartData)}}
+
+ {{#if this.tooltipContent}}
+
+ {{this.tooltipContent}}
+
+ {{/if}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsInstructorsGraphNoData"}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.instructor"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+
+
+ {{row.instructorName}}
+
+ |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 8d9d47666c..c87c0c054c 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructors-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-instructors-graph.js
@@ -7,64 +7,86 @@ 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 { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeInstructorsGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getData(this.args.course));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ get isLoaded() {
+ return this.outputData.isResolved;
}
- @use loadedData = new AsyncProcess(() => [this.getData.bind(this), this.sessions]);
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
+ }
- get chartType() {
- return this.args.chartType || 'horz-bar';
+ get hasData() {
+ return this.data.length;
}
- get filteredData() {
- if (!this.data) {
- return [];
- }
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
- let data = this.data;
- const q = cleanQuery(this.args.filter);
- if (q) {
- const exp = new RegExp(q, 'gi');
- data = this.data.filter(({ label }) => label.match(exp));
- }
+ get filteredChartData() {
+ return this.filterData(this.chartData);
+ }
+
+ get hasChartData() {
+ return this.filteredChartData.length;
+ }
+
+ get filteredData() {
+ return this.filterData(this.data);
+ }
- return data.sort((first, second) => {
- return first.data - second.data;
+ get tableData() {
+ return this.filteredData.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.instructor = obj.meta.user;
+ rhett.instructorName = obj.meta.user.fullName;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
});
}
- get data() {
- if (!this.loadedData) {
- return [];
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
}
- return this.loadedData;
+ this.sortBy = prop;
}
- async getData() {
- if (!this.sessions) {
- return [];
+ filterData(data) {
+ const q = cleanQuery(this.args.filter);
+ if (q) {
+ const exp = new RegExp(q, 'gi');
+ return data.filter(({ label }) => label.match(exp));
}
+ return data;
+ }
- const sessionsWithInstructors = await map(this.sessions.slice(), async (session) => {
+ async getData(course) {
+ const sessions = await course.sessions;
+ const sessionsWithInstructors = await map(sessions, async (session) => {
const instructors = await session.getAllInstructors();
- const totalInstructionalTime = await session.getTotalSumOfferingsDuration();
const instructorsWithInstructionalTime = await map(instructors, async (instructor) => {
const minutes = await session.getTotalSumOfferingsDurationByInstructor(instructor);
return {
@@ -73,46 +95,41 @@ export default class CourseVisualizeInstructorsGraph extends Component {
};
});
return {
- sessionTitle: session.title,
- totalInstructionalTime: Math.round(totalInstructionalTime * 60),
+ session,
instructorsWithInstructionalTime,
};
});
- const instructorData = sessionsWithInstructors.reduce((set, obj) => {
- obj.instructorsWithInstructionalTime.forEach((instructorWithInstructionalTime) => {
- const name = instructorWithInstructionalTime.instructor.get('fullName');
- const id = instructorWithInstructionalTime.instructor.get('id');
- let existing = findBy(set, 'label', name);
- if (!existing) {
- existing = {
- data: 0,
- label: name,
- meta: {
- userId: id,
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += instructorWithInstructionalTime.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
+ return sessionsWithInstructors
+ .reduce((set, { session, instructorsWithInstructionalTime }) => {
+ instructorsWithInstructionalTime.forEach(({ instructor, minutes }) => {
+ const id = instructor.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: instructor.fullName,
+ meta: {
+ user: instructor,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+ });
+ return set;
+ }, [])
+ .map((obj) => {
+ obj.description = `${obj.meta.user.fullName} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
+ return obj;
+ })
+ .sort((first, second) => {
+ return first.data - second.data;
});
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(sessionsWithInstructors, 'totalInstructionalTime').reduce(
- (total, minutes) => total + minutes,
- 0,
- );
- return instructorData.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.label}: ${obj.data} ${this.intl.t('general.minutes')}`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
- return obj;
- });
}
barHover = restartableTask(async (obj) => {
@@ -122,10 +139,12 @@ export default class CourseVisualizeInstructorsGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, meta } = obj;
- const sessions = uniqueValues(meta.sessions).sort().join(', ');
- this.tooltipTitle = htmlSafe(label);
- this.tooltipContent = htmlSafe(sessions + '
' + this.intl.t('general.clickForMore'));
+ this.tooltipTitle = htmlSafe(
+ `${obj.meta.user.fullName} • ${obj.data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(
+ uniqueValues(mapBy(obj.meta.sessions, 'title')).sort().join(', '),
+ );
});
@action
@@ -134,10 +153,6 @@ export default class CourseVisualizeInstructorsGraph extends Component {
return;
}
- this.router.transitionTo(
- 'course-visualize-instructor',
- this.args.course.get('id'),
- obj.meta.userId,
- );
+ this.router.transitionTo('course-visualize-instructor', this.args.course.id, obj.meta.user.id);
}
}
diff --git a/packages/ilios-common/addon/components/course/visualize-instructors.hbs b/packages/ilios-common/addon/components/course/visualize-instructors.hbs
index 8846870c04..a7c9fbd782 100644
--- a/packages/ilios-common/addon/components/course/visualize-instructors.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-instructors.hbs
@@ -43,7 +43,11 @@
>
-
+
{{/unless}}
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 5f99dc16f7..07960b3d0d 100644
--- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.hbs
@@ -51,77 +51,79 @@
{{/if}}
- {{/if}}
- {{#if (and (not @isIcon) @showDataTable)}}
-
-
-
-
-
- {{t "general.percentage"}}
-
-
- {{t "general.courseObjective"}}
-
-
- {{t "general.competencies"}}
-
-
- {{t "general.sessions"}}
-
-
- {{t "general.minutes"}}
-
-
-
-
- {{#each (sort-by this.sortBy this.tableData) as |row|}}
+ {{#if (and (not @isIcon) @showDataTable)}}
+
+
+
- {{row.percentageLabel}} |
- {{{row.objective}}} |
- {{row.competencies}} |
-
- {{#each row.sessions as |session index|}}
-
- {{session.title~}}
- {{if (not-eq index (sub row.sessions.length 1)) ","}}
- {{/each}}
- |
- {{row.minutes}} |
+
+ {{t "general.percentage"}}
+
+
+ {{t "general.courseObjective"}}
+
+
+ {{t "general.competencies"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
- {{/each}}
-
-
-
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+ {{row.percentageLabel}} |
+ {{{row.objective}}} |
+ {{row.competencies}} |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 176e12a260..8bee822157 100644
--- a/packages/ilios-common/addon/components/course/visualize-objectives-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-objectives-graph.js
@@ -5,9 +5,7 @@ import { service } from '@ember/service';
import { htmlSafe } from '@ember/template';
import { filter, map } from 'rsvp';
import { restartableTask, timeout } from 'ember-concurrency';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
import { mapBy, sortBy, uniqueValues } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeObjectivesGraph extends Component {
@@ -20,32 +18,29 @@ export default class CourseVisualizeObjectivesGraph extends Component {
@tracked sortBy = 'percentage:desc';
@cached
- get courseSessionsData() {
+ get sessionsData() {
return new TrackedAsyncData(this.args.course.sessions);
}
- get courseSessions() {
- return this.courseSessionsData.isResolved ? this.courseSessionsData.value : null;
+ get sessions() {
+ return this.sessionsData.isResolved ? this.sessionsData.value : [];
}
- @use dataObjects = new AsyncProcess(() => [this.getDataObjects.bind(this), this.sessions]);
+ @cached
+ get outputData() {
+ return new TrackedAsyncData(this.getDataObjects(this.sessions));
+ }
- get sortedAscending() {
- return this.sortBy.search(/desc/) === -1;
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
}
- get sessions() {
- if (!this.courseSessions) {
- return [];
- }
- return this.courseSessions.slice();
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
}
get tableData() {
- if (!this.dataObjects) {
- return [];
- }
- return this.dataObjects.map((obj) => {
+ return this.data.map((obj) => {
const rhett = {};
rhett.minutes = obj.data;
// KLUDGE!
@@ -64,15 +59,15 @@ export default class CourseVisualizeObjectivesGraph extends Component {
}
get objectiveWithMinutes() {
- return this.dataObjects?.filter((obj) => obj.data !== 0);
+ return this.data.filter((obj) => obj.data !== 0);
}
get objectiveWithoutMinutes() {
- return this.dataObjects?.filter((obj) => obj.data === 0);
+ return this.data.filter((obj) => obj.data === 0);
}
get isLoaded() {
- return !!this.dataObjects;
+ return this.outputData.isResolved;
}
@action
@@ -84,10 +79,6 @@ export default class CourseVisualizeObjectivesGraph extends Component {
}
async getDataObjects(sessions) {
- if (!sessions) {
- return [];
- }
-
const sessionsWithMinutes = sessions.map(async (session) => {
const hours = await session.getTotalSumDuration();
return {
@@ -139,14 +130,14 @@ export default class CourseVisualizeObjectivesGraph extends Component {
.filter((title) => !!title)
.sort();
const minutes = sessionCourseObjectiveMap.map((obj) => {
- if (obj.objectives.includes(courseObjective.get('id'))) {
+ if (obj.objectives.includes(courseObjective.id)) {
return obj.minutes;
} else {
return 0;
}
});
const sessionObjectives = sessionCourseObjectiveMap.filter((obj) =>
- obj.objectives.includes(courseObjective.get('id')),
+ obj.objectives.includes(courseObjective.id),
);
const meta = {
competencies: uniqueValues(competencyTitles).join(', '),
@@ -168,7 +159,12 @@ export default class CourseVisualizeObjectivesGraph extends Component {
return mappedObjectives.map((obj) => {
const percent = totalMinutes ? ((obj.data / totalMinutes) * 100).toFixed(1) : 0;
+ let objectiveTitle = obj.meta.courseObjective.title;
+ if (obj.meta.competencies) {
+ objectiveTitle += ` (${obj.meta.competencies})`;
+ }
obj.label = `${percent}%`;
+ obj.description = `${objectiveTitle} - ${obj.data} ${this.intl.t('general.minutes')}`;
obj.percentage = percent;
return obj;
});
@@ -187,11 +183,9 @@ export default class CourseVisualizeObjectivesGraph extends Component {
objectiveTitle += `(${meta.competencies})`;
}
- const title = htmlSafe(`${objectiveTitle} • ${data} ${this.intl.t('general.minutes')}`);
- const sessionTitles = mapBy(meta.sessionObjectives, 'sessionTitle');
- const content = sessionTitles.sort().join(', ');
-
- this.tooltipTitle = title;
- this.tooltipContent = content;
+ this.tooltipTitle = htmlSafe(
+ `${objectiveTitle} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(mapBy(meta.sessionObjectives, 'sessionTitle').sort().join(', '));
});
}
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 04eba89903..5b1610c25a 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
@@ -4,11 +4,11 @@
...attributes
>
{{#if this.isLoaded}}
- {{#if (or @isIcon this.data.length)}}
+ {{#if (or @isIcon this.hasChartData)}}
@@ -19,5 +19,63 @@
{{/if}}
{{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsSessionTypeGraphNoData" sessionType=@sessionType.title}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.vocabulary"}} - {{t "general.term"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+ {{row.vocabularyTerm}} |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 246b32e155..ccdef0be63 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
@@ -5,55 +5,69 @@ import { htmlSafe } from '@ember/template';
import { restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy } from 'ilios-common/utils/array-helpers';
+import { action } from '@ember/object';
export default class CourseVisualizeSessionTypeGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'vocabularyTerm';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getDataObjects(this.args.course, this.args.sessionType));
}
- @cached
- get sessionTypeSessionsData() {
- return new TrackedAsyncData(this.args.sessionType.sessions);
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
+ }
+
+ get hasData() {
+ return this.data.length;
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : [];
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
}
- get sessionTypeSessions() {
- return this.sessionTypeSessionsData.isResolved ? this.sessionTypeSessionsData.value : [];
+ get hasChartData() {
+ return this.chartData.length;
+ }
+
+ get isLoaded() {
+ return this.outputData.isResolved;
}
- @use dataObjects = new AsyncProcess(() => [
- this.getDataObjects.bind(this),
- this.sessionsAndSessionTypeSessions,
- ]);
-
- get sessionsAndSessionTypeSessions() {
- const rhett = {
- sessions: [],
- sessionTypeSessions: [],
- };
- if (this.sessions && this.sessionTypeSessions) {
- rhett.sessions = this.sessions.slice();
- rhett.sessionTypeSessions = this.sessionTypeSessions.slice();
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.vocabularyTerm = `${obj.meta.vocabulary.title} - ${obj.meta.term.title}`;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
}
- return rhett;
+ this.sortBy = prop;
}
- async getDataObjects(sessionsAndSessionTypeSessions) {
- const sessions = sessionsAndSessionTypeSessions.sessions;
- const sessionTypeSessions = sessionsAndSessionTypeSessions.sessionTypeSessions;
+ async getDataObjects(course, sessionType) {
+ const sessions = await course.sessions;
+ const sessionTypeSessions = await sessionType.sessions;
+
const courseSessionsWithSessionType = sessions.filter((session) =>
sessionTypeSessions.includes(session),
);
@@ -67,65 +81,56 @@ export default class CourseVisualizeSessionTypeGraph extends Component {
});
const termData = await map(sessionsWithMinutes, async ({ session, minutes }) => {
- const terms = (await session.terms).slice();
+ const terms = await session.terms;
return map(terms, async (term) => {
const vocabulary = await term.vocabulary;
return {
- sessionTitle: session.title,
- termTitle: term.title,
- vocabularyTitle: vocabulary.title,
+ session,
+ term,
+ vocabulary,
minutes,
};
});
});
- return termData.reduce((flattened, arr) => {
- return [...flattened, ...arr];
- }, []);
- }
-
- get data() {
- const data = this.dataObjects.reduce((set, obj) => {
- const label = obj.vocabularyTitle + ' - ' + obj.termTitle;
- let existing = findBy(set, 'label', label);
- if (!existing) {
- existing = {
- data: 0,
- label,
- meta: {
- vocabularyTitle: obj.vocabularyTitle,
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(data, 'data').reduce((total, minutes) => total + minutes, 0);
- return data
+ return termData
+ .reduce((flattened, arr) => {
+ return [...flattened, ...arr];
+ }, [])
+ .reduce((set, { vocabulary, term, session, minutes }) => {
+ const label = vocabulary.title + ' - ' + term.title;
+ const id = term.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label,
+ meta: {
+ vocabulary,
+ term,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+ return set;
+ }, [])
.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.label}: ${obj.data} ${this.intl.t('general.minutes')}`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
+ obj.description = `${obj.meta.vocabulary.title} - ${obj.meta.term.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
return obj;
})
.sort((first, second) => {
return (
- first.meta.vocabularyTitle.localeCompare(second.meta.vocabularyTitle) ||
+ first.meta.vocabulary.title.localeCompare(second.meta.vocabulary.title) ||
first.data - second.data
);
});
}
- get isLoaded() {
- return !!this.dataObjects;
- }
-
barHover = restartableTask(async (obj) => {
await timeout(100);
if (this.args.isIcon || isEmpty(obj) || obj.empty) {
@@ -133,9 +138,15 @@ export default class CourseVisualizeSessionTypeGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, meta } = obj;
- this.tooltipTitle = htmlSafe(label);
- this.tooltipContent = uniqueValues(meta.sessions).sort().join(', ');
+ const { data, meta } = obj;
+
+ const title = htmlSafe(
+ `${meta.vocabulary.title} - ${meta.term.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ const content = mapBy(meta.sessions, 'title').sort().join(', ');
+
+ this.tooltipTitle = title;
+ this.tooltipContent = content;
});
}
diff --git a/packages/ilios-common/addon/components/course/visualize-session-type.hbs b/packages/ilios-common/addon/components/course/visualize-session-type.hbs
index cdb1883077..3a5950255d 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-type.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-session-type.hbs
@@ -41,6 +41,7 @@
{{/unless}}
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 4195bb746a..5b23278aaf 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
@@ -3,20 +3,84 @@
data-test-course-visualize-session-types-graph
...attributes
>
- {{#if (or @isIcon this.data.length)}}
-
- {{#if this.tooltipContent}}
-
- {{this.tooltipContent}}
-
- {{/if}}
-
+ {{#if this.isLoaded}}
+ {{#if (or @isIcon this.hasChartData)}}
+
+ {{#if this.tooltipContent}}
+
+ {{this.tooltipContent}}
+
+ {{/if}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsNoSessions"}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.sessionType"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+
+
+ {{row.sessionTypeTitle}}
+
+ |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 f334a7b6e7..24f1c8ad7e 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
@@ -6,101 +6,124 @@ import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { cleanQuery } from 'ilios-common/utils/query-utils';
import { map } from 'rsvp';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeSessionTypesGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getData(this.args.course));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ get isLoaded() {
+ return this.outputData.isResolved;
}
- @use loadedData = new AsyncProcess(() => [this.getData.bind(this), this.sessions]);
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
+ }
+
+ get hasData() {
+ return this.data.length;
+ }
+
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
+
+ get filteredChartData() {
+ return this.filterData(this.chartData);
+ }
- get chartType() {
- return this.args.chartType || 'horz-bar';
+ get hasChartData() {
+ return this.filteredChartData.length;
}
get filteredData() {
- if (!this.data) {
- return [];
+ return this.filterData(this.data);
+ }
+
+ get tableData() {
+ return this.filteredData.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.sessionType = obj.meta.sessionType;
+ rhett.sessionTypeTitle = obj.meta.sessionType.title;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
}
- let data = this.data;
+ this.sortBy = prop;
+ }
+
+ filterData(data) {
const q = cleanQuery(this.args.filter);
if (q) {
const exp = new RegExp(q, 'gi');
- data = this.data.filter(({ label }) => label.match(exp));
+ return data.filter(({ label }) => label.match(exp));
}
- return data.sort((first, second) => {
- return first.data - second.data;
- });
+ return data;
}
- get data() {
- if (!this.loadedData) {
- return [];
- }
- return this.loadedData;
- }
+ async getData(course) {
+ const sessions = await course.sessions;
- async getData(sessions) {
- if (!sessions) {
+ if (!sessions.length) {
return [];
}
- const dataMap = await map(sessions.slice(), async (session) => {
+ const dataMap = await map(sessions, async (session) => {
const hours = await session.getTotalSumDuration();
const minutes = Math.round(hours * 60);
const sessionType = await session.sessionType;
return {
- sessionTitle: session.title,
- sessionTypeTitle: sessionType.title,
- sessionTypeId: sessionType.get('id'),
+ session,
+ sessionType,
minutes,
};
});
- const mappedSessionTypes = dataMap.reduce((set, obj) => {
- let existing = findBy(set, 'label', obj.sessionTypeTitle);
- if (!existing) {
- existing = {
- data: 0,
- label: obj.sessionTypeTitle,
- meta: {
- sessionType: obj.sessionTypeTitle,
- sessionTypeId: obj.sessionTypeId,
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(mappedSessionTypes, 'data').reduce(
- (total, minutes) => total + minutes,
- 0,
- );
- return mappedSessionTypes
+ return dataMap
+ .reduce((set, { sessionType, session, minutes }) => {
+ const id = sessionType.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: sessionType.title,
+ meta: {
+ sessionType,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+
+ return set;
+ }, [])
.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.meta.sessionType}: ${obj.data} ${this.intl.t('general.minutes')}`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
+ obj.description = `${obj.meta.sessionType.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
return obj;
})
.sort((first, second) => {
@@ -115,13 +138,11 @@ export default class CourseVisualizeSessionTypesGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, meta } = obj;
-
- const title = htmlSafe(label);
- const sessions = uniqueValues(meta.sessions).sort().join(', ');
-
- this.tooltipTitle = title;
- this.tooltipContent = sessions;
+ const { data, meta } = obj;
+ this.tooltipTitle = htmlSafe(
+ `${meta.sessionType.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(uniqueValues(mapBy(meta.sessions, 'title')).sort().join(', '));
});
@action
@@ -131,8 +152,8 @@ export default class CourseVisualizeSessionTypesGraph extends Component {
}
this.router.transitionTo(
'course-visualize-session-type',
- this.args.course.get('id'),
- obj.meta.sessionTypeId,
+ this.args.course.id,
+ obj.meta.sessionType.id,
);
}
}
diff --git a/packages/ilios-common/addon/components/course/visualize-session-types.hbs b/packages/ilios-common/addon/components/course/visualize-session-types.hbs
index 0a96c7db92..efd6a1df30 100644
--- a/packages/ilios-common/addon/components/course/visualize-session-types.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-session-types.hbs
@@ -41,6 +41,10 @@
>
-
+
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 71908bed9d..51fd37cd4c 100644
--- a/packages/ilios-common/addon/components/course/visualize-term-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-term-graph.hbs
@@ -4,11 +4,11 @@
...attributes
>
{{#if this.isLoaded}}
- {{#if (or @isIcon this.data.length)}}
+ {{#if (or @isIcon this.hasChartData)}}
@@ -19,5 +19,63 @@
{{/if}}
{{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsTermGraphNoData" term=@term.title}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.sessionType"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+ {{row.sessionType}} |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 3c9cdbd0ed..52eb7b33e9 100644
--- a/packages/ilios-common/addon/components/course/visualize-term-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-term-graph.js
@@ -1,95 +1,111 @@
import Component from '@glimmer/component';
+import { map } from 'rsvp';
import { htmlSafe } from '@ember/template';
import { restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { TrackedAsyncData } from 'ember-async-data';
-import {
- findBy,
- findById,
- mapBy,
- uniqueById,
- uniqueValues,
-} from 'ilios-common/utils/array-helpers';
+import { findById, mapBy } from 'ilios-common/utils/array-helpers';
+import { action } from '@ember/object';
export default class CourseVisualizeTermGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getDataObjects(this.args.course, this.args.term));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : null;
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
}
- @cached
- get sessionTypesData() {
- if (!this.sessionsData.isResolved) {
- return null;
- }
- return new TrackedAsyncData(Promise.all(this.sessionsData.value.map((s) => s.sessionType)));
+ get hasData() {
+ return this.data.length;
+ }
+
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
}
- get sessionTypes() {
- return this.sessionTypesData?.isResolved ? uniqueById(this.sessionTypesData.value) : null;
+ get hasChartData() {
+ return this.chartData.length;
}
get isLoaded() {
- return !!this.sessionTypes;
+ return this.outputData.isResolved;
}
- get termSessionIds() {
- return this.args.term.hasMany('sessions').ids();
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.sessionType = obj.meta.sessionType.title;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
}
- get termSessionsInCourse() {
- return this.sessions.filter((session) => this.termSessionIds.includes(session.id));
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
}
- get data() {
- const sessionTypeData = this.termSessionsInCourse.map((session) => {
- const minutes = Math.round(session.totalSumDuration * 60);
- const sessionType = findById(this.sessionTypes, session.belongsTo('sessionType').id());
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
+ }
+ this.sortBy = prop;
+ }
+
+ async getDataObjects(course, term) {
+ const sessions = await course.sessions;
+ const sessionIds = term.hasMany('sessions').ids();
+ const filteredSessions = sessions.filter((session) => sessionIds.includes(session.id));
+ const sessionTypes = await Promise.all(filteredSessions.map((s) => s.sessionType));
+ const sessionTypeData = await map(filteredSessions, async (session) => {
+ const hours = await session.getTotalSumDuration();
+ const sessionType = findById(sessionTypes, session.belongsTo('sessionType').id());
return {
- sessionTitle: session.title,
- sessionTypeTitle: sessionType.title,
- minutes,
+ session,
+ sessionType,
+ minutes: Math.round(hours * 60),
};
});
- const data = sessionTypeData.reduce((set, obj) => {
- let existing = findBy(set, 'label', obj.sessionTypeTitle);
- if (!existing) {
- existing = {
- data: 0,
- label: obj.sessionTypeTitle,
- meta: {
- sessionTypeTitle: obj.sessionTypeTitle,
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(data, 'data').reduce((total, minutes) => total + minutes, 0);
-
- return data.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.meta.sessionTypeTitle} ${percent}%`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
- return obj;
- });
+ return sessionTypeData
+ .reduce((set, { sessionType, session, minutes }) => {
+ const id = sessionType.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: sessionType.title,
+ meta: {
+ sessionType: sessionType,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+ return set;
+ }, [])
+ .map((obj) => {
+ obj.description = `${obj.meta.sessionType.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
+ return obj;
+ })
+ .sort((first, second) => {
+ return first.data - second.data;
+ });
}
barHover = restartableTask(async (obj) => {
@@ -99,9 +115,10 @@ export default class CourseVisualizeTermGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, data, meta } = obj;
-
- this.tooltipTitle = htmlSafe(`${label} ${data} ${this.intl.t('general.minutes')}`);
- this.tooltipContent = uniqueValues(meta.sessions).sort().join(', ');
+ const { data, meta } = obj;
+ this.tooltipTitle = htmlSafe(
+ `${meta.sessionType.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
}
diff --git a/packages/ilios-common/addon/components/course/visualize-term.hbs b/packages/ilios-common/addon/components/course/visualize-term.hbs
index ca55d32074..136d0d4d89 100644
--- a/packages/ilios-common/addon/components/course/visualize-term.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-term.hbs
@@ -46,7 +46,11 @@
-
+
{{/unless}}
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 3a761a5c1d..166414ed68 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.hbs
@@ -4,14 +4,14 @@
...attributes
>
{{#if this.isLoaded}}
- {{#if (or @isIcon this.data.length)}}
+ {{#if (or @isIcon this.hasChartData)}}
{{#if this.tooltipContent}}
@@ -20,5 +20,67 @@
{{/if}}
{{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsNoSessions"}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.vocabulary"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+
+
+ {{row.vocabularyTitle}}
+
+ |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 5b891eb2fd..6f89b2155c 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-vocabularies-graph.js
@@ -5,93 +5,142 @@ import { restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy, uniqueById } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeVocabulariesGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getDataObjects(this.args.course));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : [];
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
+ }
+
+ get hasData() {
+ return this.data.length;
+ }
+
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
+
+ get hasChartData() {
+ return this.chartData.length;
}
- @use dataObjects = new AsyncProcess(() => [this.getDataObjects.bind(this), this.sessions]);
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ 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;
+ });
+ }
get isLoaded() {
- return !!this.dataObjects;
+ return this.outputData.isResolved;
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
}
- async getDataObjects(sessions) {
- if (!sessions) {
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
+ }
+ this.sortBy = prop;
+ }
+
+ async getDataObjects(course) {
+ const sessions = await course.sessions;
+ if (!sessions.length) {
return [];
}
- const sessionsWithMinutes = await map(sessions.slice(), async (session) => {
+
+ const sessionsWithMinutes = await map(sessions, async (session) => {
const hours = await session.getTotalSumDuration();
return {
session,
minutes: Math.round(hours * 60),
};
});
- return map(sessionsWithMinutes, async ({ session, minutes }) => {
- const terms = (await session.terms).slice();
- const vocabularies = await all(mapBy(terms, 'vocabulary'));
- return {
- sessionTitle: session.title,
- vocabularies,
- minutes,
- };
- });
- }
- get data() {
- return this.dataObjects.reduce((set, obj) => {
- obj.vocabularies.forEach((vocabulary) => {
- const vocabularyTitle = vocabulary.get('title');
- let existing = findBy(set, 'label', vocabularyTitle);
- if (!existing) {
- existing = {
- data: 0,
- label: vocabularyTitle,
- meta: {
- vocabulary,
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += obj.minutes;
- existing.meta.sessions.push(obj.sessionTitle);
- });
+ const sessionWithMinutesAndVocabs = await map(
+ sessionsWithMinutes,
+ async ({ session, minutes }) => {
+ const terms = await session.terms;
+ const vocabularies = await all(mapBy(terms, 'vocabulary'));
+ return {
+ session,
+ vocabularies: uniqueById(vocabularies),
+ minutes,
+ };
+ },
+ );
+
+ return sessionWithMinutesAndVocabs
+ .reduce((set, { session, vocabularies, minutes }) => {
+ vocabularies.forEach((vocabulary) => {
+ const id = vocabulary.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: vocabulary.title,
+ meta: {
+ vocabulary,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+ });
- return set;
- }, []);
+ return set;
+ }, [])
+ .map((obj) => {
+ obj.description = `${obj.meta.vocabulary.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
+ return obj;
+ })
+ .sort((first, second) => {
+ return first.data - second.data;
+ });
}
- donutHover = restartableTask(async (obj) => {
+ barHover = restartableTask(async (obj) => {
await timeout(100);
if (this.args.isIcon || !obj || obj.empty) {
this.tooltipTitle = null;
this.tooltipContent = null;
return;
}
- const { meta } = obj;
-
- this.tooltipTitle = htmlSafe(meta.vocabulary.get('title'));
- this.tooltipContent = this.intl.t('general.clickForMore');
+ const { data, meta } = obj;
+ this.tooltipTitle = htmlSafe(
+ `${meta.vocabulary.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
@action
- donutClick(obj) {
+ barClick(obj) {
if (this.args.isIcon || !obj || obj.empty || !obj.meta) {
return;
}
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabularies.hbs b/packages/ilios-common/addon/components/course/visualize-vocabularies.hbs
index 4845865d9c..537e4fdee7 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabularies.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabularies.hbs
@@ -32,6 +32,6 @@
-
+
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 d68e28cd81..828daf18cd 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.hbs
@@ -4,7 +4,7 @@
...attributes
>
{{#if this.isLoaded}}
- {{#if (or @isIcon this.data.length)}}
+ {{#if (or @isIcon this.hasChartData)}}
{{/if}}
+ {{#if (and (not @isIcon) (not this.hasData))}}
+
+ {{t "general.courseVisualizationsVocabularyGraphNoData" vocabulary=@vocabulary.title}}
+
+ {{/if}}
+ {{#if (and (not @isIcon) this.hasData @showDataTable)}}
+
+
+
+
+
+ {{t "general.term"}}
+
+
+ {{t "general.sessions"}}
+
+
+ {{t "general.minutes"}}
+
+
+
+
+ {{#each (sort-by this.sortBy this.tableData) as |row|}}
+
+
+
+ {{row.termTitle}}
+
+ |
+
+ {{#each row.sessions as |session index|}}
+
+ {{session.title~}}
+ {{if (not-eq index (sub row.sessions.length 1)) ","}}
+ {{/each}}
+ |
+ {{row.minutes}} |
+
+ {{/each}}
+
+
+
+ {{/if}}
+ {{else}}
+
{{/if}}
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 a1f2b0e9e4..687f2d4452 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js
+++ b/packages/ilios-common/addon/components/course/visualize-vocabulary-graph.js
@@ -5,99 +5,126 @@ import { restartableTask, timeout } from 'ember-concurrency';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { use } from 'ember-could-get-used-to-this';
import { TrackedAsyncData } from 'ember-async-data';
-import AsyncProcess from 'ilios-common/classes/async-process';
-import { findBy, mapBy, uniqueValues } from 'ilios-common/utils/array-helpers';
+import { findById, mapBy } from 'ilios-common/utils/array-helpers';
export default class CourseVisualizeVocabularyGraph extends Component {
@service router;
@service intl;
@tracked tooltipContent = null;
@tracked tooltipTitle = null;
+ @tracked sortBy = 'minutes';
@cached
- get sessionsData() {
- return new TrackedAsyncData(this.args.course.sessions);
+ get outputData() {
+ return new TrackedAsyncData(this.getDataObjects(this.args.course));
}
- get sessions() {
- return this.sessionsData.isResolved ? this.sessionsData.value : [];
+ get data() {
+ return this.outputData.isResolved ? this.outputData.value : [];
+ }
+
+ get hasData() {
+ return this.data.length;
+ }
+
+ get chartData() {
+ return this.data.filter((obj) => obj.data);
+ }
+
+ get hasChartData() {
+ return this.chartData.length;
}
- @use dataObjects = new AsyncProcess(() => [this.getDataObjects.bind(this), this.sessions]);
+ get tableData() {
+ return this.data.map((obj) => {
+ const rhett = {};
+ rhett.minutes = obj.data;
+ rhett.sessions = obj.meta.sessions;
+ rhett.term = obj.meta.term;
+ rhett.termTitle = obj.meta.term.title;
+ rhett.sessionTitles = mapBy(rhett.sessions, 'title').join(', ');
+ return rhett;
+ });
+ }
get isLoaded() {
- return !!this.dataObjects;
+ return this.outputData.isResolved;
+ }
+
+ get sortedAscending() {
+ return this.sortBy.search(/desc/) === -1;
+ }
+
+ @action
+ setSortBy(prop) {
+ if (this.sortBy === prop) {
+ prop += ':desc';
+ }
+ this.sortBy = prop;
}
- async getDataObjects(sessions) {
- if (!sessions) {
+ async getDataObjects(course) {
+ const sessions = await course.sessions;
+ if (!sessions.length) {
return [];
}
- const sessionsWithMinutes = await map(sessions.slice(), async (session) => {
+ const sessionsWithMinutes = await map(sessions, async (session) => {
const hours = await session.getTotalSumDuration();
return {
session,
minutes: Math.round(hours * 60),
};
});
- const terms = await map(sessionsWithMinutes, async ({ session, minutes }) => {
- const sessionTerms = await session.get('terms');
- const sessionTermsInThisVocabulary = await filter(sessionTerms.slice(), async (term) => {
- const termVocab = await term.get('vocabulary');
- return termVocab.get('id') === this.args.vocabulary.get('id');
- });
- return sessionTermsInThisVocabulary.map((term) => {
- return {
- term,
- session: {
- title: session.get('title'),
+ const termsWithSessionAndMinutes = await map(
+ sessionsWithMinutes,
+ async ({ session, minutes }) => {
+ const sessionTerms = await session.terms;
+ const sessionTermsInThisVocabulary = await filter(sessionTerms.slice(), async (term) => {
+ const termVocab = await term.vocabulary;
+ return termVocab.id === this.args.vocabulary.id;
+ });
+ return sessionTermsInThisVocabulary.map((term) => {
+ return {
+ term,
+ session,
minutes,
- },
- };
- });
- });
-
- return terms.reduce((flattened, arr) => {
- return [...flattened, ...arr];
- }, []);
- }
-
- get data() {
- const termData = this.dataObjects.reduce((set, { term, session }) => {
- const termTitle = term.get('title');
- let existing = findBy(set, 'label', termTitle);
- if (!existing) {
- existing = {
- data: 0,
- label: termTitle,
- meta: {
- termTitle,
- termId: term.get('id'),
- sessions: [],
- },
- };
- set.push(existing);
- }
- existing.data += session.minutes;
- existing.meta.sessions.push(session.title);
-
- return set;
- }, []);
-
- const totalMinutes = mapBy(termData, 'data').reduce((total, minutes) => total + minutes, 0);
- const mappedTermsWithLabel = termData.map((obj) => {
- const percent = ((obj.data / totalMinutes) * 100).toFixed(1);
- obj.label = `${obj.meta.termTitle}: ${obj.data} ${this.intl.t('general.minutes')}`;
- obj.meta.totalMinutes = totalMinutes;
- obj.meta.percent = percent;
- return obj;
- });
+ };
+ });
+ },
+ );
- return mappedTermsWithLabel.sort((first, second) => {
- return first.data - second.data;
- });
+ return termsWithSessionAndMinutes
+ .reduce((flattened, arr) => {
+ return [...flattened, ...arr];
+ }, [])
+ .reduce((set, { term, session, minutes }) => {
+ const id = term.id;
+ let existing = findById(set, id);
+ if (!existing) {
+ existing = {
+ id,
+ data: 0,
+ label: term.title,
+ meta: {
+ term,
+ sessions: [],
+ },
+ };
+ set.push(existing);
+ }
+ existing.data += minutes;
+ existing.meta.sessions.push(session);
+ return set;
+ }, [])
+ .map((obj) => {
+ obj.description = `${obj.meta.term.title} - ${obj.data} ${this.intl.t('general.minutes')}`;
+ delete obj.id;
+ return obj;
+ })
+ .sort((first, second) => {
+ return first.data - second.data;
+ });
}
barHover = restartableTask(async (obj) => {
@@ -107,10 +134,11 @@ export default class CourseVisualizeVocabularyGraph extends Component {
this.tooltipContent = null;
return;
}
- const { label, meta } = obj;
-
- this.tooltipTitle = htmlSafe(label);
- this.tooltipContent = uniqueValues(meta.sessions).sort().join(', ');
+ const { data, meta } = obj;
+ this.tooltipTitle = htmlSafe(
+ `${meta.term.title} • ${data} ${this.intl.t('general.minutes')}`,
+ );
+ this.tooltipContent = htmlSafe(mapBy(meta.sessions, 'title').sort().join(', '));
});
@action
@@ -118,6 +146,6 @@ export default class CourseVisualizeVocabularyGraph extends Component {
if (this.args.isIcon || !obj || obj.empty || !obj.meta) {
return;
}
- this.router.transitionTo('course-visualize-term', this.args.course.id, obj.meta.termId);
+ this.router.transitionTo('course-visualize-term', this.args.course.id, obj.meta.term.id);
}
}
diff --git a/packages/ilios-common/addon/components/course/visualize-vocabulary.hbs b/packages/ilios-common/addon/components/course/visualize-vocabulary.hbs
index 8745ba8416..33925dec1e 100644
--- a/packages/ilios-common/addon/components/course/visualize-vocabulary.hbs
+++ b/packages/ilios-common/addon/components/course/visualize-vocabulary.hbs
@@ -40,6 +40,7 @@
diff --git a/packages/ilios-common/app/styles/ilios-common/components.scss b/packages/ilios-common/app/styles/ilios-common/components.scss
index 602b004faa..b8b30cdd2f 100644
--- a/packages/ilios-common/app/styles/ilios-common/components.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components.scss
@@ -131,6 +131,7 @@
@import "components/course/visualizations";
@import "components/course/visualize-instructor";
@import "components/course/visualize-instructor-session-type-graph";
+@import "components/course/visualize-instructor-term-graph";
@import "components/course/visualize-instructors";
@import "components/course/visualize-instructors-graph";
@import "components/course/visualize-objectives";
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualizations.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualizations.scss
index 1458eba80f..ab03a7671a 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualizations.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualizations.scss
@@ -4,13 +4,25 @@
@include m.data-visualization;
.visualizations {
+ @include m.for-tablet-and-up {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ margin-bottom: 1rem;
+
.course-visualize-instructors-graph,
.course-visualize-objectives-graph,
.course-visualize-session-types-graph,
.course-visualize-vocabularies-graph {
- height: 40vh;
- margin-bottom: 2rem;
- width: 40vw;
+ display: inline-block;
+ height: 100%;
+ text-align: center;
+ width: 100%;
+
+ .simple-chart {
+ height: 250px;
+ width: 250px;
+ }
}
}
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-session-type-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-session-type-graph.scss
index 4d8794c5cf..3b458ff58b 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-session-type-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-session-type-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-instructor-session-type-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-instructor-session-type-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-term-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-term-graph.scss
new file mode 100644
index 0000000000..6f131e323c
--- /dev/null
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor-term-graph.scss
@@ -0,0 +1,5 @@
+@use "../../mixins" as m;
+
+.course-visualize-instructor-term-graph {
+ @include m.graph-with-data-table;
+}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor.scss
index 9c4e14bb4f..7803f1a70b 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructor.scss
@@ -3,16 +3,11 @@
.course-visualize-instructor {
@include m.data-visualization;
- .visualizations {
- .course-visualize-instructor-session-type-graph,
- .course-visualize-instructor-term-graph {
- height: 80vh;
- width: 80vw;
-
- @include m.for-laptop-and-up {
- height: 40vh;
- width: 40vw;
- }
+ @include m.for-laptop-and-up {
+ .visualizations {
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: 1fr 1fr;
}
}
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructors-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructors-graph.scss
index a8a1964a2e..1347eed10f 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructors-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-instructors-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-instructors-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-instructors-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-objectives-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-objectives-graph.scss
index f94b5d938a..a1515b6bc7 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-objectives-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-objectives-graph.scss
@@ -1,10 +1,7 @@
-@use "../../colors" as c;
@use "../../mixins" as m;
.course-visualize-objectives-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
+ @include m.graph-with-data-table;
.with-hours {
p {
@@ -16,15 +13,21 @@
}
}
- .zero-hours {
+ .objective-row {
p {
- margin-top: 0.5rem;
+ margin: 0;
}
+ }
+ .zero-hours {
h4 {
@include m.ilios-heading-h4;
}
+ p {
+ margin-top: 0.5rem;
+ }
+
li {
list-style-type: disc;
margin-left: 1rem;
@@ -36,49 +39,7 @@
}
}
- .data-table {
- grid-column: -1/1;
- padding-top: 2rem;
-
- table {
- @include m.ilios-table-structure;
- @include m.ilios-table-colors;
- @include m.ilios-removable-table;
- @include m.ilios-zebra-table;
-
- thead {
- background-color: c.$culturedGrey;
- }
-
- td {
- vertical-align: top;
- }
-
- .objective {
- p {
- margin: 0;
- }
- }
- }
- }
-
&.not-icon {
- display: grid;
grid-template-columns: 2fr 1fr;
- height: auto;
- width: auto;
-
- .simple-chart {
- height: 80vh;
- }
-
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
}
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-type-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-type-graph.scss
index 33c08b03d2..16cec328ee 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-type-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-type-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-session-type-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-session-type-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-types-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-types-graph.scss
index e9609c27e8..1a076e206c 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-types-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-session-types-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-session-types-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-session-types-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-term-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-term-graph.scss
index 65a2808359..7d15d1547a 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-term-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-term-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-term-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-term-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabularies-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabularies-graph.scss
index f8c3f14484..13b8d8f0ea 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabularies-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabularies-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-vocabularies-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-vocabularies-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabulary-graph.scss b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabulary-graph.scss
index 08009c5f0e..97ea18087a 100644
--- a/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabulary-graph.scss
+++ b/packages/ilios-common/app/styles/ilios-common/components/course/visualize-vocabulary-graph.scss
@@ -1,19 +1,5 @@
-.course-visualize-vocabulary-graph {
- display: inline-block;
- height: 1rem;
- width: 1rem;
-
- &.not-icon {
- height: 75vh;
- width: 75vw;
+@use "../../mixins" as m;
- .simple-chart-tooltip {
- .title {
- p {
- margin: 0;
- padding: 0;
- }
- }
- }
- }
+.course-visualize-vocabulary-graph {
+ @include m.graph-with-data-table;
}
diff --git a/packages/ilios-common/app/styles/ilios-common/mixins.scss b/packages/ilios-common/app/styles/ilios-common/mixins.scss
index 398c8b174e..f067526e86 100644
--- a/packages/ilios-common/app/styles/ilios-common/mixins.scss
+++ b/packages/ilios-common/app/styles/ilios-common/mixins.scss
@@ -5,6 +5,7 @@
@forward "mixins/data-visualization";
@forward "mixins/detail-container";
@forward "mixins/font-size";
+@forward "mixins/graph-with-data-table";
@forward "mixins/icon";
@forward "mixins/ilios-button";
@forward "mixins/ilios-form";
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 bfbaa989b7..f3150802d2 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
@@ -30,10 +30,9 @@
}
.visualizations {
- display: flex;
- flex-wrap: wrap;
- justify-items: center;
- margin-top: 2rem;
- padding-left: 0.8rem;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-gap: 10px;
+ margin-left: 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
new file mode 100644
index 0000000000..a42d1f9f17
--- /dev/null
+++ b/packages/ilios-common/app/styles/ilios-common/mixins/graph-with-data-table.scss
@@ -0,0 +1,51 @@
+@use "../colors" as c;
+@use "ilios-table" as t;
+
+@mixin graph-with-data-table() {
+ display: inline-block;
+ height: 1rem;
+ width: 1rem;
+
+ .data-table {
+ grid-column: -1/1;
+ padding-top: 2rem;
+
+ table {
+ @include t.ilios-table-structure;
+ @include t.ilios-table-colors;
+ @include t.ilios-removable-table;
+ @include t.ilios-zebra-table;
+
+ thead {
+ background-color: c.$culturedGrey;
+ }
+
+ td {
+ vertical-align: top;
+ }
+ }
+ }
+
+ &.not-icon {
+ display: grid;
+ height: auto;
+ width: auto;
+
+ .simple-chart {
+ height: 80vh;
+ }
+
+ .simple-chart-tooltip {
+ .title {
+ p {
+ margin: 0;
+ padding: 0;
+ }
+ }
+ }
+ }
+
+ .no-data {
+ text-align: center;
+ }
+}
diff --git a/packages/ilios-common/translations/en-us.yaml b/packages/ilios-common/translations/en-us.yaml
index 0a9649519a..24adc83dde 100644
--- a/packages/ilios-common/translations/en-us.yaml
+++ b/packages/ilios-common/translations/en-us.yaml
@@ -87,6 +87,12 @@ general:
courseTitle: Course Title
courseTitlePlaceholder: Enter a title for this course
courseVisualizations: Course Visualizations
+ courseVisualizationsNoSessions: "This course has no sessions."
+ courseVisualizationsInstructorNoData: "{instructor} is not instructing any sessions in this this course."
+ courseVisualizationsInstructorsGraphNoData: No instructors have been linked to any sessions in this course.
+ courseVisualizationsSessionTypeGraphNoData: "No vocabulary terms have been linked to any {sessionType} sessions in this course."
+ courseVisualizationsTermGraphNoData: "The vocabulary term {term} has not been linked to any sessions in this course."
+ courseVisualizationsVocabularyGraphNoData: "No {vocabulary} vocabulary terms have been linked to any sessions in this course."
currentlySearchingPrompt: searching...
dashboardNavigation: Dashboard navigation
date: Date
diff --git a/packages/ilios-common/translations/es.yaml b/packages/ilios-common/translations/es.yaml
index cc93e4ac3a..51b8729b51 100644
--- a/packages/ilios-common/translations/es.yaml
+++ b/packages/ilios-common/translations/es.yaml
@@ -87,6 +87,12 @@ general:
courseTitle: Titulo de Curso
courseTitlePlaceholder: Entre en un tÃtulo para este curso
courseVisualizations: Visualizaciones de Cursos
+ courseVisualizationsNoSessions: Este curso no tiene sesiones.
+ courseVisualizationsInstructorNoData: "{instructor} no está impartiendo ninguna sesión en este curso."
+ courseVisualizationsInstructorsGraphNoData: No se han vinculado instructores a ninguna sesión de este curso.
+ courseVisualizationsSessionTypeGraphNoData: "No se han vinculado términos de vocabulario a ninguna sesión {sessionType} en este curso."
+ courseVisualizationsTermGraphNoData: "El término de vocabulario {term} no se ha vinculado a ninguna sesión de este curso."
+ courseVisualizationsVocabularyGraphNoData: "No se han vinculado términos del vocabulario {vocabulary} a ninguna sesión de este curso."
currentlySearchingPrompt: buscando...
dashboardNavigation: Panel de navegación
date: Fecha
diff --git a/packages/ilios-common/translations/fr.yaml b/packages/ilios-common/translations/fr.yaml
index 19c14c0624..7d840436bf 100644
--- a/packages/ilios-common/translations/fr.yaml
+++ b/packages/ilios-common/translations/fr.yaml
@@ -87,6 +87,12 @@ general:
courseTitle: Titre de Cours
courseTitlePlaceholder: Ajoutez titre par ce cours
courseVisualizations: Cours Visualisations
+ courseVisualizationsNoSessions: Ce cours n'a pas de séances.
+ courseVisualizationsInstructorNoData: "{instructor} n'enseigne aucune séance dans ce cours."
+ courseVisualizationsInstructorsGraphNoData: Aucun instructeur n'a été lié à aucune session de ce cours.
+ courseVisualizationsSessionTypeGraphNoData: "Aucun terme de vocabulaire n'a été lié à une session {sessionType} dans ce cours."
+ courseVisualizationsTermGraphNoData: "Le terme de vocabulaire {term} n'a été lié à aucune session de ce cours."
+ courseVisualizationsVocabularyGraphNoData: "Aucun terme de vocabulaire {vocabulary} n'a été lié à aucune session de ce cours."
currentlySearchingPrompt: Recherchent...
dashboardNavigation: Naviguer dans le tableau de bord
date: Date
diff --git a/packages/test-app/tests/integration/components/course/visualize-instructor-session-type-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-instructor-session-type-graph-test.js
index 71672d6ffa..b56686be35 100644
--- a/packages/test-app/tests/integration/components/course/visualize-instructor-session-type-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-instructor-session-type-graph-test.js
@@ -11,7 +11,7 @@ module(
setupRenderingTest(hooks);
setupMirage(hooks);
- test('it renders', async function (assert) {
+ hooks.beforeEach(async function () {
const instructor = this.server.create('user');
const sessionType1 = this.server.create('session-type', {
title: 'Standalone',
@@ -19,17 +19,26 @@ module(
const sessionType2 = this.server.create('session-type', {
title: 'Campaign',
});
- const course = this.server.create('course');
+ const sessionType3 = this.server.create('session-type', {
+ title: 'Prelude',
+ });
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
sessionType: sessionType1,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
sessionType: sessionType2,
});
+ const session3 = this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
+ sessionType: sessionType3,
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -48,27 +57,156 @@ module(
endDate: new Date('2019-12-05T21:00:00'),
instructors: [instructor],
});
-
- const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
- const instructorModel = await this.owner
+ this.server.create('offering', {
+ session: session3,
+ startDate: new Date('2019-12-05T18:00:00'),
+ endDate: new Date('2019-12-05T18:00:00'),
+ instructors: [instructor],
+ });
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
.lookup('service:store')
- .findRecord('user', instructor.id);
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.instructor = await this.owner.lookup('service:store').findRecord('user', instructor.id);
+ });
+
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.instructor);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.noData.isVisible);
+ await waitFor('.loaded');
+ await waitFor('svg .slice');
+ assert.strictEqual(component.chart.slices.length, 2);
+ assert.strictEqual(component.chart.descriptions.length, 2);
+ assert.strictEqual(component.chart.descriptions[0].text, 'Campaign - 180 Minutes');
+ assert.strictEqual(component.chart.descriptions[1].text, 'Standalone - 630 Minutes');
+ assert.strictEqual(component.chart.labels.length, 2);
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.chart.labels[1].text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows.length, 2);
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].url,
+ '/courses/1/sessions/2',
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].url,
+ '/courses/1/sessions/1',
+ );
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
- this.set('course', courseModel);
- this.set('instructor', instructorModel);
+ test('sort data-table by session type', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.instructor);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Campaign');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ });
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.instructor);
await render(
- hbs`
+ hbs`
`,
);
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ });
- //let the chart animations finish
- await waitFor('.loaded');
- await waitFor('svg .chart .slice');
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.instructor);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
- assert.strictEqual(component.chart.slices.length, 2);
- assert.strictEqual(component.chart.slices[0].text, 'Standalone 77.8%');
- assert.strictEqual(component.chart.slices[1].text, 'Campaign 22.2%');
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ this.set('instructor', this.instructor);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ '0 guy M. Mc0son is not instructing any sessions in this this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ this.set('instructor', this.instructor);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].url,
+ '/courses/2/sessions/3',
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
},
);
diff --git a/packages/test-app/tests/integration/components/course/visualize-instructor-term-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-instructor-term-graph-test.js
index 6710586853..948c54447e 100644
--- a/packages/test-app/tests/integration/components/course/visualize-instructor-term-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-instructor-term-graph-test.js
@@ -9,7 +9,7 @@ module('Integration | Component | course/visualize-instructor-term-graph', funct
setupRenderingTest(hooks);
setupMirage(hooks);
- test('it renders', async function (assert) {
+ hooks.beforeEach(async function () {
const instructor = this.server.create('user');
const vocabulary1 = this.server.create('vocabulary');
const vocabulary2 = this.server.create('vocabulary');
@@ -21,20 +21,31 @@ module('Integration | Component | course/visualize-instructor-term-graph', funct
vocabulary: vocabulary2,
title: 'Campaign',
});
+ const term3 = this.server.create('term', {
+ vocabulary: vocabulary2,
+ title: 'Prelude',
+ });
const sessionType = this.server.create('session-type');
- const course = this.server.create('course');
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
terms: [term1],
sessionType: sessionType,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
terms: [term2],
sessionType: sessionType,
});
+ const session3 = this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
+ terms: [term3],
+ sessionType: sessionType,
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -53,24 +64,148 @@ module('Integration | Component | course/visualize-instructor-term-graph', funct
endDate: new Date('2019-12-05T21:00:00'),
instructors: [instructor],
});
+ this.server.create('offering', {
+ session: session3,
+ startDate: new Date('2019-12-05T18:00:00'),
+ endDate: new Date('2019-12-05T18:00:00'),
+ instructors: [instructor],
+ });
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.user = await this.owner.lookup('service:store').findRecord('user', instructor.id);
+ });
- const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
- const userModel = await this.owner.lookup('service:store').findRecord('user', instructor.id);
-
- this.set('course', courseModel);
- this.set('instructor', userModel);
-
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.user);
await render(
- hbs`
+ hbs`
`,
);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(
+ component.chart.bars[0].description,
+ 'Vocabulary 1 - Standalone - 630 Minutes',
+ );
+ assert.strictEqual(
+ component.chart.bars[1].description,
+ 'Vocabulary 2 - Campaign - 180 Minutes',
+ );
assert.strictEqual(component.chart.labels.length, 2);
- assert.strictEqual(component.chart.labels[0].text, 'Vocabulary 1 > Standalone: 630 Minutes');
- assert.strictEqual(component.chart.labels[1].text, 'Vocabulary 2 > Campaign: 180 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.chart.labels[1].text, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('sort data-table by vocabulary term', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.user);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ await component.dataTable.header.vocabularyTerm.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ await component.dataTable.header.vocabularyTerm.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ });
+
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.user);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('instructor', this.user);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ this.set('instructor', this.user);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ '0 guy M. Mc0son is not instructing any sessions in this this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ this.set('instructor', this.user);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-instructor-test.js b/packages/test-app/tests/integration/components/course/visualize-instructor-test.js
index 75f46f0399..5d7d975ee7 100644
--- a/packages/test-app/tests/integration/components/course/visualize-instructor-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-instructor-test.js
@@ -139,23 +139,15 @@ module('Integration | Component | course/visualize-instructor', function (hooks)
// wait for charts to load
await waitFor('.loaded');
await waitFor('svg .bars');
- await waitFor('svg .chart');
+ await waitFor('svg .slice');
assert.strictEqual(component.termsChart.chart.bars.length, 3);
assert.strictEqual(component.termsChart.chart.labels.length, 3);
- assert.strictEqual(
- component.termsChart.chart.labels[0].text,
- 'Vocabulary 1 > term 0: 60 Minutes',
- );
- assert.strictEqual(
- component.termsChart.chart.labels[1].text,
- 'Vocabulary 1 > term 1: 30 Minutes',
- );
- assert.strictEqual(
- component.termsChart.chart.labels[2].text,
- 'Vocabulary 2 > term 2: 30 Minutes',
- );
+ assert.strictEqual(component.termsChart.chart.labels[0].text, 'Vocabulary 1 - term 0');
+ assert.strictEqual(component.termsChart.chart.labels[1].text, 'Vocabulary 1 - term 1');
+ assert.strictEqual(component.termsChart.chart.labels[2].text, 'Vocabulary 2 - term 2');
assert.strictEqual(component.sessionTypesChart.chart.slices.length, 2);
- assert.strictEqual(component.sessionTypesChart.chart.slices[0].text, 'session type 0 66.7%');
- assert.strictEqual(component.sessionTypesChart.chart.slices[1].text, 'session type 1 33.3%');
+ assert.strictEqual(component.sessionTypesChart.chart.labels.length, 2);
+ assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'session type 1');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[1].text, 'session type 0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-instructors-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-instructors-graph-test.js
index 3cdf92565c..921503f83d 100644
--- a/packages/test-app/tests/integration/components/course/visualize-instructors-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-instructors-graph-test.js
@@ -13,18 +13,22 @@ module('Integration | Component | course/visualize-instructors-graph', function
const instructor1 = this.server.create('user', { displayName: 'Marie' });
const instructor2 = this.server.create('user', { displayName: 'Daisy' });
const instructor3 = this.server.create('user', { displayName: 'Duke' });
- const instructor4 = this.server.create('user', {
- displayName: 'William',
- });
+ const instructor4 = this.server.create('user', { displayName: 'William' });
+ const instructor5 = this.server.create('user', { displayName: 'Roland' });
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
- const course = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
+ });
+ const session3 = this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
});
this.server.create('offering', {
session: session1,
@@ -44,59 +48,209 @@ module('Integration | Component | course/visualize-instructors-graph', function
endDate: new Date('2019-12-05T21:00:00'),
instructors: [instructor1, instructor2, instructor3, instructor4],
});
-
- this.courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
+ this.server.create('offering', {
+ session: session3,
+ startDate: new Date('2019-12-08T12:00:00'),
+ endDate: new Date('2019-12-08T12:00:00'),
+ instructors: [instructor5],
+ });
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
});
test('it renders', async function (assert) {
- this.set('course', this.courseModel);
-
- await render(hbs`
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
`);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 4);
+ assert.strictEqual(component.chart.bars[0].description, 'Daisy - 180 Minutes');
+ assert.strictEqual(component.chart.bars[1].description, 'Duke - 180 Minutes');
+ assert.strictEqual(component.chart.bars[2].description, 'William - 510 Minutes');
+ assert.strictEqual(component.chart.bars[3].description, 'Marie - 810 Minutes');
assert.strictEqual(component.chart.labels.length, 4);
- assert.strictEqual(component.chart.labels[0].text, 'Daisy: 180 Minutes');
- assert.strictEqual(component.chart.labels[1].text, 'Duke: 180 Minutes');
- assert.strictEqual(component.chart.labels[2].text, 'William: 510 Minutes');
- assert.strictEqual(component.chart.labels[3].text, 'Marie: 810 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Daisy');
+ assert.strictEqual(component.chart.labels[1].text, 'Duke');
+ assert.strictEqual(component.chart.labels[2].text, 'William');
+ assert.strictEqual(component.chart.labels[3].text, 'Marie');
+ assert.strictEqual(component.dataTable.rows.length, 4);
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'Daisy');
+ assert.strictEqual(component.dataTable.rows[0].instructor.url, '/data/courses/1/instructors/2');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].instructor.text, 'Duke');
+ assert.strictEqual(component.dataTable.rows[1].instructor.url, '/data/courses/1/instructors/3');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].instructor.text, 'William');
+ assert.strictEqual(component.dataTable.rows[2].instructor.url, '/data/courses/1/instructors/4');
+ assert.strictEqual(component.dataTable.rows[2].sessions.links.length, 2);
+ assert.strictEqual(
+ component.dataTable.rows[2].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[2].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(
+ component.dataTable.rows[2].sessions.links[1].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[2].sessions.links[1].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '510');
+ assert.strictEqual(component.dataTable.rows[3].instructor.text, 'Marie');
+ assert.strictEqual(component.dataTable.rows[3].instructor.url, '/data/courses/1/instructors/1');
+ assert.strictEqual(component.dataTable.rows[3].sessions.links.length, 2);
+ assert.strictEqual(
+ component.dataTable.rows[3].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[3].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(
+ component.dataTable.rows[3].sessions.links[1].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[3].sessions.links[1].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[3].minutes, '810');
});
test('filter applies', async function (assert) {
this.set('name', 'Marie');
- this.set('course', this.courseModel);
-
+ this.set('course', this.linkedCourseWithTime);
await render(
- hbs`
+ hbs`
`,
);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 1);
assert.strictEqual(component.chart.labels.length, 1);
- assert.strictEqual(component.chart.labels[0].text, 'Marie: 810 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Marie');
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'Marie');
});
- test('it renders as donut chart', async function (assert) {
- this.set('course', this.courseModel);
+ test('sort data-table by instructor', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'Daisy');
+ assert.strictEqual(component.dataTable.rows[1].instructor.text, 'Duke');
+ assert.strictEqual(component.dataTable.rows[2].instructor.text, 'William');
+ assert.strictEqual(component.dataTable.rows[3].instructor.text, 'Marie');
+ await component.dataTable.header.instructor.toggle();
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'Daisy');
+ assert.strictEqual(component.dataTable.rows[1].instructor.text, 'Duke');
+ assert.strictEqual(component.dataTable.rows[2].instructor.text, 'Marie');
+ assert.strictEqual(component.dataTable.rows[3].instructor.text, 'William');
+ await component.dataTable.header.instructor.toggle();
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'William');
+ assert.strictEqual(component.dataTable.rows[1].instructor.text, 'Marie');
+ assert.strictEqual(component.dataTable.rows[2].instructor.text, 'Duke');
+ assert.strictEqual(component.dataTable.rows[3].instructor.text, 'Daisy');
+ });
- await render(
- hbs`
-`,
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(
+ component.dataTable.rows[2].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
);
- //let the chart animations finish
- await waitFor('.loaded');
- await waitFor('svg .slice');
+ assert.strictEqual(
+ component.dataTable.rows[3].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
+ );
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
+ );
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[3].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(
+ component.dataTable.rows[2].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
+ );
+ assert.strictEqual(
+ component.dataTable.rows[3].sessions.text,
+ 'Berkeley Investigations, The San Leandro Horror',
+ );
+ });
- assert.strictEqual(component.chart.slices.length, 4);
- assert.strictEqual(component.chart.slices[0].text, 'Daisy: 180 Minutes');
- assert.strictEqual(component.chart.slices[1].text, 'Duke: 180 Minutes');
- assert.strictEqual(component.chart.slices[2].text, 'William: 510 Minutes');
- assert.strictEqual(component.chart.slices[3].text, 'Marie: 810 Minutes');
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '510');
+ assert.strictEqual(component.dataTable.rows[3].minutes, '810');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '810');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '510');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[3].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '510');
+ assert.strictEqual(component.dataTable.rows[3].minutes, '810');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ await render(hbs`
+`);
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ 'No instructors have been linked to any sessions in this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ await render(hbs`
+`);
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].instructor.text, 'Roland');
+ assert.strictEqual(component.dataTable.rows[0].instructor.url, '/data/courses/2/instructors/5');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-instructors-test.js b/packages/test-app/tests/integration/components/course/visualize-instructors-test.js
index e6467c6fe3..fd05c73851 100644
--- a/packages/test-app/tests/integration/components/course/visualize-instructors-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-instructors-test.js
@@ -55,10 +55,10 @@ module('Integration | Component | course/visualize-instructors', function (hooks
await waitFor('svg .bars');
assert.strictEqual(component.instructorsChart.chart.bars.length, 4);
assert.strictEqual(component.instructorsChart.chart.labels.length, 4);
- assert.strictEqual(component.instructorsChart.chart.labels[0].text, 'Daisy: 180 Minutes');
- assert.strictEqual(component.instructorsChart.chart.labels[1].text, 'Duke: 180 Minutes');
- assert.strictEqual(component.instructorsChart.chart.labels[2].text, 'William: 510 Minutes');
- assert.strictEqual(component.instructorsChart.chart.labels[3].text, 'Marie: 810 Minutes');
+ assert.strictEqual(component.instructorsChart.chart.labels[0].text, 'Daisy');
+ assert.strictEqual(component.instructorsChart.chart.labels[1].text, 'Duke');
+ assert.strictEqual(component.instructorsChart.chart.labels[2].text, 'William');
+ assert.strictEqual(component.instructorsChart.chart.labels[3].text, 'Marie');
});
test('filter works', async function (assert) {
@@ -85,21 +85,12 @@ module('Integration | Component | course/visualize-instructors', function (hooks
assert.strictEqual(component.title, 'course 0 2021');
assert.strictEqual(component.instructorsChart.chart.bars.length, 2);
assert.strictEqual(component.instructorsChart.chart.labels.length, 2);
- assert.strictEqual(
- component.instructorsChart.chart.labels[0].text,
- 'foo M. Mc0son: 1440 Minutes',
- );
- assert.strictEqual(
- component.instructorsChart.chart.labels[1].text,
- 'bar M. Mc1son: 1440 Minutes',
- );
+ assert.strictEqual(component.instructorsChart.chart.labels[0].text, 'foo M. Mc0son');
+ assert.strictEqual(component.instructorsChart.chart.labels[1].text, 'bar M. Mc1son');
await component.filter.set('foo');
assert.strictEqual(component.instructorsChart.chart.bars.length, 1);
assert.strictEqual(component.instructorsChart.chart.labels.length, 1);
- assert.strictEqual(
- component.instructorsChart.chart.labels[0].text,
- 'foo M. Mc0son: 1440 Minutes',
- );
+ assert.strictEqual(component.instructorsChart.chart.labels[0].text, 'foo M. Mc0son');
});
test('course year is shown as range if applicable by configuration', async function (assert) {
diff --git a/packages/test-app/tests/integration/components/course/visualize-objectives-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-objectives-graph-test.js
index d9b3215479..19613e80f5 100644
--- a/packages/test-app/tests/integration/components/course/visualize-objectives-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-objectives-graph-test.js
@@ -91,8 +91,16 @@ module('Integration | Component | course/visualize-objectives-graph', function (
await waitFor('svg .slice');
assert.strictEqual(component.chart.slices.length, 2);
- assert.strictEqual(component.chart.slices[0].text, '77.8%');
- assert.strictEqual(component.chart.slices[1].text, '22.2%');
+ assert.strictEqual(component.chart.slices[0].label, '77.8%');
+ assert.strictEqual(
+ component.chart.slices[0].description,
+ 'course objective 0 (competency 0, competency 1) - 630 Minutes',
+ );
+ assert.strictEqual(component.chart.slices[1].label, '22.2%');
+ assert.strictEqual(
+ component.chart.slices[1].description,
+ 'course objective 1 (competency 1) - 180 Minutes',
+ );
assert.notOk(component.unlinkedObjectives.isPresent);
assert.strictEqual(component.untaughtObjectives.items.length, 1);
assert.strictEqual(component.untaughtObjectives.items[0].text, 'course objective 2');
diff --git a/packages/test-app/tests/integration/components/course/visualize-objectives-test.js b/packages/test-app/tests/integration/components/course/visualize-objectives-test.js
index 601fb35717..9f2cde4fe6 100644
--- a/packages/test-app/tests/integration/components/course/visualize-objectives-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-objectives-test.js
@@ -112,8 +112,16 @@ module('Integration | Component | course/visualize-objectives', function (hooks)
await waitFor('svg .slice');
assert.strictEqual(component.objectivesChart.chart.slices.length, 2);
- assert.strictEqual(component.objectivesChart.chart.slices[0].text, '77.8%');
- assert.strictEqual(component.objectivesChart.chart.slices[1].text, '22.2%');
+ assert.strictEqual(component.objectivesChart.chart.slices[0].label, '77.8%');
+ assert.strictEqual(
+ component.objectivesChart.chart.slices[0].description,
+ 'course objective 0 - 630 Minutes',
+ );
+ assert.strictEqual(component.objectivesChart.chart.slices[1].label, '22.2%');
+ assert.strictEqual(
+ component.objectivesChart.chart.slices[1].description,
+ 'course objective 1 - 180 Minutes',
+ );
assert.notOk(component.objectivesChart.unlinkedObjectives.isPresent);
assert.strictEqual(component.objectivesChart.untaughtObjectives.items.length, 1);
assert.strictEqual(
diff --git a/packages/test-app/tests/integration/components/course/visualize-session-type-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-session-type-graph-test.js
index c75b6ecc47..b9ba86776b 100644
--- a/packages/test-app/tests/integration/components/course/visualize-session-type-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-session-type-graph-test.js
@@ -9,7 +9,7 @@ module('Integration | Component | course/visualize-session-type-graph', function
setupRenderingTest(hooks);
setupMirage(hooks);
- test('it renders', async function (assert) {
+ hooks.beforeEach(async function () {
const vocabulary1 = this.server.create('vocabulary');
const vocabulary2 = this.server.create('vocabulary');
const term1 = this.server.create('term', {
@@ -20,19 +20,36 @@ module('Integration | Component | course/visualize-session-type-graph', function
vocabulary: vocabulary2,
title: 'Campaign',
});
+ const term3 = this.server.create('term', {
+ vocabulary: vocabulary2,
+ title: 'Prelude',
+ });
const sessionType = this.server.create('session-type');
- const course = this.server.create('course');
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
terms: [term1],
- sessionType: sessionType,
+ sessionType,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
+ terms: [term2],
+ sessionType,
+ });
+ this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithTime,
+ terms: [term3],
+ sessionType,
+ });
+ this.server.create('session', {
+ title: 'Peanut Butter Stout',
+ course: linkedCourseWithoutTime,
terms: [term2],
- sessionType: sessionType,
+ sessionType,
});
this.server.create('offering', {
session: session1,
@@ -49,26 +66,163 @@ module('Integration | Component | course/visualize-session-type-graph', function
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
-
- const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
- const sessionTypeModel = await this.owner
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.sessionType = await this.owner
.lookup('service:store')
.findRecord('session-type', sessionType.id);
+ });
- this.set('course', courseModel);
- this.set('type', sessionTypeModel);
-
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('type', this.sessionType);
await render(
- hbs`
+ hbs`
`,
);
//let the chart animations finish
+ assert.notOk(component.noData.isVisible);
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(
+ component.chart.bars[0].description,
+ 'Vocabulary 1 - Standalone - 630 Minutes',
+ );
+ assert.strictEqual(
+ component.chart.bars[1].description,
+ 'Vocabulary 2 - Campaign - 180 Minutes',
+ );
assert.strictEqual(component.chart.labels.length, 2);
- assert.strictEqual(component.chart.labels[0].text, 'Vocabulary 1 - Standalone: 630 Minutes');
- assert.strictEqual(component.chart.labels[1].text, 'Vocabulary 2 - Campaign: 180 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.chart.labels[1].text, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows.length, 3);
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[2].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[2].sessions.links[0].url, '/courses/1/sessions/3');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '0');
+ });
+
+ test('sort data-table by vocabulary term', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('type', this.sessionType);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[2].vocabularyTerm, 'Vocabulary 2 - Prelude');
+ await component.dataTable.header.vocabularyTerm.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Prelude');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[2].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ await component.dataTable.header.vocabularyTerm.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 1 - Standalone');
+ assert.strictEqual(component.dataTable.rows[1].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[2].vocabularyTerm, 'Vocabulary 2 - Prelude');
+ });
+
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('type', this.sessionType);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Two Slices of Pizza');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Two Slices of Pizza');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Two Slices of Pizza');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Berkeley Investigations');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('type', this.sessionType);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '0');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '0');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ this.set('type', this.sessionType);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ 'No vocabulary terms have been linked to any session type 0 sessions in this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ this.set('type', this.sessionType);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].vocabularyTerm, 'Vocabulary 2 - Campaign');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Peanut Butter Stout');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/4');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-session-types-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-session-types-graph-test.js
index 2766862fb5..38f2f94a2e 100644
--- a/packages/test-app/tests/integration/components/course/visualize-session-types-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-session-types-graph-test.js
@@ -16,17 +16,31 @@ module('Integration | Component | course/visualize-session-types-graph', functio
const sessionType2 = this.server.create('session-type', {
title: 'Campaign',
});
- const course = this.server.create('course');
+ const sessionType3 = this.server.create('session-type', {
+ title: 'Prelude',
+ });
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
sessionType: sessionType1,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
sessionType: sessionType2,
});
+ this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithTime,
+ sessionType: sessionType3,
+ });
+ this.server.create('session', {
+ title: 'Peanut Butter Stout',
+ course: linkedCourseWithoutTime,
+ sessionType: sessionType3,
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -42,55 +56,173 @@ module('Integration | Component | course/visualize-session-types-graph', functio
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
-
- this.courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
});
- test('it renders as bar chart by default', async function (assert) {
- this.set('course', this.courseModel);
-
- await render(hbs`
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
`);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(component.chart.bars[0].description, 'Campaign - 180 Minutes');
+ assert.strictEqual(component.chart.bars[1].description, 'Standalone - 630 Minutes');
assert.strictEqual(component.chart.labels.length, 2);
- assert.strictEqual(component.chart.labels[0].text, 'Campaign: 180 Minutes');
- assert.strictEqual(component.chart.labels[1].text, 'Standalone: 630 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.chart.labels[1].text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows.length, 3);
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Prelude');
+ assert.strictEqual(
+ component.dataTable.rows[0].sessionType.url,
+ '/data/courses/1/session-types/3',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
+ assert.strictEqual(component.dataTable.rows[1].sessionType.text, 'Campaign');
+ assert.strictEqual(
+ component.dataTable.rows[1].sessionType.url,
+ '/data/courses/1/session-types/2',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].sessionType.text, 'Standalone');
+ assert.strictEqual(
+ component.dataTable.rows[2].sessionType.url,
+ '/data/courses/1/session-types/1',
+ );
+ assert.strictEqual(component.dataTable.rows[2].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[2].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[2].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '630');
});
- test('it renders as donut chart', async function (assert) {
- this.set('course', this.courseModel);
-
+ test('filter applies', async function (assert) {
+ this.set('title', 'Campaign');
+ this.set('course', this.linkedCourseWithTime);
await render(
- hbs`
+ hbs`
`,
);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
- await waitFor('svg .slice');
+ await waitFor('svg .bars');
+ assert.strictEqual(component.chart.bars.length, 1);
+ assert.strictEqual(component.chart.labels.length, 1);
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Campaign');
+ });
+
+ test('filter out all data', async function (assert) {
+ this.set('title', 'Geflarknik');
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.ok(component.dataTable.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 0);
+ });
- assert.strictEqual(component.chart.slices.length, 2);
- assert.strictEqual(component.chart.slices[0].text, 'Campaign: 180 Minutes');
- assert.strictEqual(component.chart.slices[1].text, 'Standalone: 630 Minutes');
+ test('sort data-table by session type', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[1].sessionType.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[2].sessionType.text, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[2].sessionType.text, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessionType.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[2].sessionType.text, 'Campaign');
});
- test('filter applies', async function (assert) {
- this.set('title', 'Campaign');
- this.set('course', this.courseModel);
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Two Slices of Pizza');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[2].sessions.text, 'Berkeley Investigations');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(hbs`
+`);
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '0');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[2].minutes, '630');
+ });
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
await render(
- hbs`
+ hbs`
`,
);
- //let the chart animations finish
- await waitFor('.loaded');
- await waitFor('svg .bars');
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(component.noData.text, 'This course has no sessions.');
+ });
- assert.strictEqual(component.chart.bars.length, 1);
- assert.strictEqual(component.chart.labels.length, 1);
- assert.strictEqual(component.chart.labels[0].text, 'Campaign: 180 Minutes');
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessionType.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Peanut Butter Stout');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/4');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-session-types-test.js b/packages/test-app/tests/integration/components/course/visualize-session-types-test.js
index 7f7bed2469..aad3534af2 100644
--- a/packages/test-app/tests/integration/components/course/visualize-session-types-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-session-types-test.js
@@ -45,11 +45,11 @@ module('Integration | Component | course/visualize-session-types', function (hoo
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
- this.courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
+ this.course = await this.owner.lookup('service:store').findRecord('course', course.id);
});
test('it renders', async function (assert) {
- this.set('course', this.courseModel);
+ this.set('course', this.course);
await render(hbs`
`);
assert.strictEqual(component.title, 'course 0 2021');
@@ -58,8 +58,8 @@ module('Integration | Component | course/visualize-session-types', function (hoo
await waitFor('svg .bars');
assert.strictEqual(component.sessionTypesChart.chart.bars.length, 2);
assert.strictEqual(component.sessionTypesChart.chart.labels.length, 2);
- assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign: 180 Minutes');
- assert.strictEqual(component.sessionTypesChart.chart.labels[1].text, 'Standalone: 630 Minutes');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[1].text, 'Standalone');
});
test('course year is shown as range if applicable by configuration', async function (assert) {
@@ -70,14 +70,14 @@ module('Integration | Component | course/visualize-session-types', function (hoo
},
};
});
- this.set('course', this.courseModel);
+ this.set('course', this.course);
await render(hbs`
`);
assert.strictEqual(component.title, 'course 0 2021 - 2022');
});
test('filter works', async function (assert) {
- this.set('course', this.courseModel);
+ this.set('course', this.course);
await render(hbs`
`);
//let the chart animations finish
@@ -86,16 +86,16 @@ module('Integration | Component | course/visualize-session-types', function (hoo
assert.strictEqual(component.title, 'course 0 2021');
assert.strictEqual(component.sessionTypesChart.chart.bars.length, 2);
assert.strictEqual(component.sessionTypesChart.chart.labels.length, 2);
- assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign: 180 Minutes');
- assert.strictEqual(component.sessionTypesChart.chart.labels[1].text, 'Standalone: 630 Minutes');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[1].text, 'Standalone');
await component.filter.set('Campaign');
assert.strictEqual(component.sessionTypesChart.chart.bars.length, 1);
assert.strictEqual(component.sessionTypesChart.chart.labels.length, 1);
- assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign: 180 Minutes');
+ assert.strictEqual(component.sessionTypesChart.chart.labels[0].text, 'Campaign');
});
test('breadcrumb', async function (assert) {
- this.set('course', this.courseModel);
+ this.set('course', this.course);
await render(hbs`
`);
diff --git a/packages/test-app/tests/integration/components/course/visualize-term-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-term-graph-test.js
index 0ee602e55a..9cdf6f9aa7 100644
--- a/packages/test-app/tests/integration/components/course/visualize-term-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-term-graph-test.js
@@ -1,35 +1,46 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'test-app/tests/helpers';
-import { render, findAll, waitFor } from '@ember/test-helpers';
+import { render, waitFor } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'test-app/tests/test-support/mirage';
+import { component } from 'ilios-common/page-objects/components/course/visualize-term-graph';
module('Integration | Component | course/visualize-term-graph', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
- test('it renders', async function (assert) {
+ hooks.beforeEach(async function () {
const vocabulary = this.server.create('vocabulary');
const term = this.server.create('term', { vocabulary });
- const course = this.server.create('course');
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const sessionType1 = this.server.create('session-type', {
title: 'Standalone',
});
const sessionType2 = this.server.create('session-type', {
title: 'Campaign',
});
+ const sessionType3 = this.server.create('session-type', {
+ title: 'Prelude',
+ });
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
terms: [term],
sessionType: sessionType1,
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
terms: [term],
sessionType: sessionType2,
});
+ this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
+ terms: [term],
+ sessionType: sessionType3,
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -45,24 +56,137 @@ module('Integration | Component | course/visualize-term-graph', function (hooks)
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.term = await this.owner.lookup('service:store').findRecord('term', term.id);
+ });
- const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
- const termModel = await this.owner.lookup('service:store').findRecord('term', term.id);
-
- this.set('course', courseModel);
- this.set('term', termModel);
-
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('term', this.term);
await render(
- hbs`
+ hbs`
`,
);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
+ assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(component.chart.bars[0].description, 'Campaign - 180 Minutes');
+ assert.strictEqual(component.chart.bars[1].description, 'Standalone - 630 Minutes');
+ assert.strictEqual(component.chart.labels.length, 2);
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.chart.labels[1].text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows.length, 2);
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
- const chartLabels = 'svg .bars text';
- assert.dom(chartLabels).exists({ count: 2 });
- assert.dom(findAll(chartLabels)[0]).hasText('Standalone 77.8%');
- assert.dom(findAll(chartLabels)[1]).hasText('Campaign 22.2%');
+ test('sort data-table by session type', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Campaign');
+ await component.dataTable.header.sessionType.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].sessionType, 'Standalone');
+ });
+
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ this.set('term', this.term);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ 'The vocabulary term term 0 has not been linked to any sessions in this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ this.set('term', this.term);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessionType, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-vocabularies-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-vocabularies-graph-test.js
index 3e5f8c0fab..0c690d3be8 100644
--- a/packages/test-app/tests/integration/components/course/visualize-vocabularies-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-vocabularies-graph-test.js
@@ -9,26 +9,36 @@ module('Integration | Component | course/visualize-vocabularies-graph', function
setupRenderingTest(hooks);
setupMirage(hooks);
- test('it renders', async function (assert) {
+ hooks.beforeEach(async function () {
const vocabulary1 = this.server.create('vocabulary', {
title: 'Standalone',
});
const vocabulary2 = this.server.create('vocabulary', {
title: 'Campaign',
});
+ const vocabulary3 = this.server.create('vocabulary', {
+ title: 'Prelude',
+ });
const term1 = this.server.create('term', { vocabulary: vocabulary1 });
const term2 = this.server.create('term', { vocabulary: vocabulary2 });
- const course = this.server.create('course');
+ const term3 = this.server.create('term', { vocabulary: vocabulary3 });
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
terms: [term1],
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
terms: [term2],
});
+ this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
+ terms: [term3],
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -44,18 +54,139 @@ module('Integration | Component | course/visualize-vocabularies-graph', function
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ });
- const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
-
- this.set('course', courseModel);
-
- await render(hbs`
-`);
+ test('it renders', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
- await waitFor('svg .slice');
- assert.strictEqual(component.chart.slices.length, 2);
- assert.strictEqual(component.chart.slices[0].text, 'Standalone');
- assert.strictEqual(component.chart.slices[1].text, 'Campaign');
+ await waitFor('svg .bars');
+ assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(component.chart.bars[0].description, 'Campaign - 180 Minutes');
+ assert.strictEqual(component.chart.bars[1].description, 'Standalone - 630 Minutes');
+ assert.strictEqual(component.chart.labels.length, 2);
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.chart.labels[1].text, 'Standalone');
+
+ assert.strictEqual(component.dataTable.rows.length, 2);
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Campaign');
+ assert.strictEqual(
+ component.dataTable.rows[0].vocabulary.url,
+ '/data/courses/1/vocabularies/2',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].vocabulary.text, 'Standalone');
+ assert.strictEqual(
+ component.dataTable.rows[1].vocabulary.url,
+ '/data/courses/1/vocabularies/1',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('sort data-table by vocabulary', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].vocabulary.text, 'Standalone');
+ await component.dataTable.header.vocabulary.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].vocabulary.text, 'Standalone');
+ await component.dataTable.header.vocabulary.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].vocabulary.text, 'Campaign');
+ await component.dataTable.header.vocabulary.toggle();
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].vocabulary.text, 'Standalone');
+ });
+
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(component.noData.text, 'This course has no sessions.');
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ await render(
+ hbs`
+`,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].vocabulary.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-vocabulary-graph-test.js b/packages/test-app/tests/integration/components/course/visualize-vocabulary-graph-test.js
index 7ad084c60d..6b8d51610c 100644
--- a/packages/test-app/tests/integration/components/course/visualize-vocabulary-graph-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-vocabulary-graph-test.js
@@ -19,17 +19,27 @@ module('Integration | Component | course/visualize-vocabulary-graph', function (
vocabulary,
title: 'Campaign',
});
- const course = this.server.create('course');
+ const term3 = this.server.create('term', {
+ vocabulary,
+ title: 'Prelude',
+ });
+ const linkedCourseWithTime = this.server.create('course');
+ const linkedCourseWithoutTime = this.server.create('course');
const session1 = this.server.create('session', {
title: 'Berkeley Investigations',
- course,
+ course: linkedCourseWithTime,
terms: [term1],
});
const session2 = this.server.create('session', {
title: 'The San Leandro Horror',
- course,
+ course: linkedCourseWithTime,
terms: [term2],
});
+ this.server.create('session', {
+ title: 'Two Slices of Pizza',
+ course: linkedCourseWithoutTime,
+ terms: [term3],
+ });
this.server.create('offering', {
session: session1,
startDate: new Date('2019-12-08T12:00:00'),
@@ -45,28 +55,140 @@ module('Integration | Component | course/visualize-vocabulary-graph', function (
startDate: new Date('2019-12-05T18:00:00'),
endDate: new Date('2019-12-05T21:00:00'),
});
-
- this.courseModel = await this.owner.lookup('service:store').findRecord('course', course.id);
- this.vocabularyModel = await this.owner
+ this.emptyCourse = await this.owner
+ .lookup('service:store')
+ .findRecord('course', this.server.create('course').id);
+ this.linkedCourseWithTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithTime.id);
+ this.linkedCourseWithoutTime = await this.owner
+ .lookup('service:store')
+ .findRecord('course', linkedCourseWithoutTime.id);
+ this.vocabulary = await this.owner
.lookup('service:store')
.findRecord('vocabulary', vocabulary.id);
});
test('it renders', async function (assert) {
- this.set('course', this.courseModel);
- this.set('vocabulary', this.vocabularyModel);
-
+ this.set('course', this.linkedCourseWithTime);
+ this.set('vocabulary', this.vocabulary);
await render(
- hbs`
+ hbs`
`,
);
+ assert.notOk(component.noData.isVisible);
//let the chart animations finish
await waitFor('.loaded');
await waitFor('svg .bars');
-
assert.strictEqual(component.chart.bars.length, 2);
+ assert.strictEqual(component.chart.bars[0].description, 'Campaign - 180 Minutes');
+ assert.strictEqual(component.chart.bars[1].description, 'Standalone - 630 Minutes');
assert.strictEqual(component.chart.labels.length, 2);
- assert.strictEqual(component.chart.labels[0].text, 'Campaign: 180 Minutes');
- assert.strictEqual(component.chart.labels[1].text, 'Standalone: 630 Minutes');
+ assert.strictEqual(component.chart.labels[0].text, 'Campaign');
+ assert.strictEqual(component.chart.labels[1].text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows.length, 2);
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[0].term.url, '/data/courses/1/terms/2');
+
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[0].sessions.links[0].text,
+ 'The San Leandro Horror',
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/1/sessions/2');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].term.text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].term.url, '/data/courses/1/terms/1');
+ assert.strictEqual(component.dataTable.rows[1].sessions.links.length, 1);
+ assert.strictEqual(
+ component.dataTable.rows[1].sessions.links[0].text,
+ 'Berkeley Investigations',
+ );
+ assert.strictEqual(component.dataTable.rows[1].sessions.links[0].url, '/courses/1/sessions/1');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('sort data-table by term', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('vocabulary', this.vocabulary);
+ await render(
+ hbs``,
+ );
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].term.text, 'Standalone');
+ await component.dataTable.header.term.toggle();
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].term.text, 'Standalone');
+ await component.dataTable.header.term.toggle();
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Standalone');
+ assert.strictEqual(component.dataTable.rows[1].term.text, 'Campaign');
+ await component.dataTable.header.term.toggle();
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Campaign');
+ assert.strictEqual(component.dataTable.rows[1].term.text, 'Standalone');
+ });
+
+ test('sort data-table by sessions', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('vocabulary', this.vocabulary);
+ await render(
+ hbs``,
+ );
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'The San Leandro Horror');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'Berkeley Investigations');
+ await component.dataTable.header.sessions.toggle();
+ assert.strictEqual(component.dataTable.rows[0].sessions.text, 'Berkeley Investigations');
+ assert.strictEqual(component.dataTable.rows[1].sessions.text, 'The San Leandro Horror');
+ });
+
+ test('sort data-table by minutes', async function (assert) {
+ this.set('course', this.linkedCourseWithTime);
+ this.set('vocabulary', this.vocabulary);
+ await render(
+ hbs``,
+ );
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '630');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '180');
+ await component.dataTable.header.minutes.toggle();
+ assert.strictEqual(component.dataTable.rows[0].minutes, '180');
+ assert.strictEqual(component.dataTable.rows[1].minutes, '630');
+ });
+
+ test('no data', async function (assert) {
+ this.set('course', this.emptyCourse);
+ this.set('vocabulary', this.vocabulary);
+ await render(
+ hbs``,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.dataTable.isVisible);
+ assert.strictEqual(
+ component.noData.text,
+ 'No Vocabulary 1 vocabulary terms have been linked to any sessions in this course.',
+ );
+ });
+
+ test('only zero time data', async function (assert) {
+ this.set('course', this.linkedCourseWithoutTime);
+ this.set('vocabulary', this.vocabulary);
+ await render(
+ hbs``,
+ );
+ assert.notOk(component.chart.isVisible);
+ assert.notOk(component.noData.isVisible);
+ assert.strictEqual(component.dataTable.rows.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].term.text, 'Prelude');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links.length, 1);
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].text, 'Two Slices of Pizza');
+ assert.strictEqual(component.dataTable.rows[0].sessions.links[0].url, '/courses/2/sessions/3');
+ assert.strictEqual(component.dataTable.rows[0].minutes, '0');
});
});
diff --git a/packages/test-app/tests/integration/components/course/visualize-vocabulary-test.js b/packages/test-app/tests/integration/components/course/visualize-vocabulary-test.js
index 392063790f..7d084d7bb1 100644
--- a/packages/test-app/tests/integration/components/course/visualize-vocabulary-test.js
+++ b/packages/test-app/tests/integration/components/course/visualize-vocabulary-test.js
@@ -91,7 +91,7 @@ module('Integration | Component | course/visualize-vocabulary', function (hooks)
await waitFor('svg .bars');
assert.strictEqual(component.termsChart.chart.bars.length, 2);
assert.strictEqual(component.termsChart.chart.labels.length, 2);
- assert.strictEqual(component.termsChart.chart.labels[0].text, 'term 1: 60 Minutes');
- assert.strictEqual(component.termsChart.chart.labels[1].text, 'term 0: 150 Minutes');
+ assert.strictEqual(component.termsChart.chart.labels[0].text, 'term 1');
+ assert.strictEqual(component.termsChart.chart.labels[1].text, 'term 0');
});
});