diff --git a/.env.example.test b/.env.example.test
index 72a0e0735..1d2d50026 100644
--- a/.env.example.test
+++ b/.env.example.test
@@ -20,4 +20,4 @@ PARENT_EMAIL='your-parent-email'
PARENT_PASSWORD='your-parent-password'
# Firebase App Check Tokens
-VITE_APPCHECK_DEBUG_TOKEN='your-firebase-app-check-debug-token'
\ No newline at end of file
+VITE_APPCHECK_DEBUG_TOKEN='your-firebase-app-check-debug-token'
diff --git a/.github/workflows/cypress-partner-admin-tests.yml b/.github/workflows/cypress-partner-admin-tests.yml
index eefcbe86d..b8f11f2f0 100644
--- a/.github/workflows/cypress-partner-admin-tests.yml
+++ b/.github/workflows/cypress-partner-admin-tests.yml
@@ -37,6 +37,10 @@ jobs:
PARTICIPANT_PASSWORD: ${{ secrets.PARTICIPANT_PASSWORD }}
PARTICIPANT_EMAIL: ${{ secrets.PARTICIPANT_EMAIL }}
PARTICIPANT_EMAIL_PASSWORD: ${{ secrets.PARTICIPANT_EMAIL_PASSWORD }}
+ PARENT_FIRST_NAME: ${{ secrets.PARENT_FIRST_NAME }}
+ PARENT_LAST_NAME: ${{ secrets.PARENT_LAST_NAME }}
+ PARENT_EMAIL: ${{ secrets.PARENT_EMAIL }}
+ PARENT_PASSWORD: ${{ secrets.PARENT_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_APPCHECK_DEBUG_TOKEN: ${{ secrets.VITE_APPCHECK_DEBUG_TOKEN }}
steps:
diff --git a/cypress.config.cjs b/cypress.config.cjs
index 794d54b15..aad8acf07 100644
--- a/cypress.config.cjs
+++ b/cypress.config.cjs
@@ -139,6 +139,10 @@ module.exports = defineConfig({
testOptionalRoarAppsAdministrationId: 'Fuy4nQaMu6YmfNg1eBYH',
testSpanishRoarAppsAdministration: 'Cypress Test Spanish Roar Apps Administration',
testSpanishRoarAppsAdministrationId: '',
+ parentFirstName: process.env.PARENT_FIRST_NAME,
+ parentLastName: process.env.PARENT_LAST_NAME,
+ parentEmail: process.env.PARENT_EMAIL,
+ parentPassword: process.env.PARENT_PASSWORD,
// Generate a list of test users CypressTestStudent0, CypressTestStudent1, ..., CypressTestStudent50 and push the test_legal_doc user
testUserList: (() => {
const list = Array.from({ length: 51 }, (_, i) => `CypressTestStudent${i}`);
diff --git a/cypress/e2e/partner-admin/default-tests/exportSelectedScoreReport.cy.js b/cypress/e2e/partner-admin/default-tests/exportSelectedScoreReport.cy.js
index 51a97aeb9..5d94f2e05 100644
--- a/cypress/e2e/partner-admin/default-tests/exportSelectedScoreReport.cy.js
+++ b/cypress/e2e/partner-admin/default-tests/exportSelectedScoreReport.cy.js
@@ -14,7 +14,7 @@ describe('The partner admin can select and export progress reports for a given a
cy.get('button').contains('Export Selected').click();
cy.readFile(
- `${Cypress.env('cypressDownloads')}/roar-scores-partner-test-administration-cypress-test-district-selected.csv`,
+ `${Cypress.env('cypressDownloads')}/roar-scores-selected-partner-test-administration-cypress-test-district.csv`,
);
});
});
diff --git a/cypress/e2e/partner-admin/default-tests/parentSignUpInvitationCode.cy.js b/cypress/e2e/partner-admin/default-tests/parentSignUpInvitationCode.cy.js
new file mode 100644
index 000000000..aa165f01b
--- /dev/null
+++ b/cypress/e2e/partner-admin/default-tests/parentSignUpInvitationCode.cy.js
@@ -0,0 +1,67 @@
+const baseUrl = Cypress.env('baseUrl');
+import { APP_ROUTES } from '../../../../src/constants/routes';
+
+const orgs = [
+ {
+ tabName: 'Districts',
+ orgName: Cypress.env('testPartnerDistrictName'),
+ orgVerified: 'Districts - Cypress Test District',
+ },
+];
+
+function visitSignUpPage(activationCode) {
+ const registerUrl = `${baseUrl}/register/?code=${activationCode}`;
+ cy.visit(registerUrl);
+}
+
+function inputLoginValues() {
+ cy.get('input[name="firstName"]').type(Cypress.env('parentFirstName'));
+ cy.get('input[name="lastName"]').type(Cypress.env('parentLastName'));
+ cy.get('input[name="ParentEmail"]').type(Cypress.env('parentEmail'));
+ cy.get('input[type="password"]').first().type(Cypress.env('parentPassword'));
+ cy.get('input[type="password"]').eq(1).type(Cypress.env('parentPassword'));
+}
+
+function completeParentSignUp(org) {
+ cy.get('div.p-checkbox-box').click();
+ cy.get('button').contains('Continue').click();
+ cy.get('button').contains('Next').click();
+ cy.get('h2').should('contain.text', org.orgVerified);
+}
+
+describe('The partner admin user', () => {
+ beforeEach(() => {
+ cy.login(Cypress.env('partnerAdminUsername'), Cypress.env('partnerAdminPassword'));
+ cy.visit(APP_ROUTES.HOME);
+ cy.visit(APP_ROUTES.LIST_ORGS);
+ });
+
+ orgs.forEach((org) => {
+ context(`when navigating to the ${org.tabName} tab`, () => {
+ it(`should see the organization ${org.orgName} and should click on Invite Users`, () => {
+ cy.checkOrgExists(org);
+
+ // Locate the row with the orgName and click the "Invite Users" button specifically for that org
+ cy.contains('td', org.orgName)
+ .parents('tr')
+ .find('button')
+ .contains('Invite Users') // Ensure the button contains the text "Invite Users"
+ .click();
+
+ cy.log(`Invite Users button clicked for ${org.orgName}.`);
+
+ // Invoke the activation code input field to get the value
+ cy.get('[data-cy="input-text-activation-code"]')
+ .invoke('attr', 'value')
+ .then((value) => {
+ expect(value).to.not.be.empty;
+
+ // Visit the sign-up page with the activation code
+ visitSignUpPage(value);
+ inputLoginValues();
+ completeParentSignUp(org);
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/e2e/pre-release-tests/testOpenAdministrations.cy.js b/cypress/e2e/pre-release-tests/testOpenAdministrations.cy.js
index a1c3575dd..739f09972 100644
--- a/cypress/e2e/pre-release-tests/testOpenAdministrations.cy.js
+++ b/cypress/e2e/pre-release-tests/testOpenAdministrations.cy.js
@@ -105,7 +105,7 @@
// language: 'es',
// },
// {
-// name: 'Words and Pictures Game',
+// name: 'ROAR - Syntax',
// app: 'core-tasks',
// spec: playSyntax,
// language: 'en',
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index ebb330d15..ed15d2ab0 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -303,6 +303,29 @@ Cypress.Commands.add('playOptionalGame', (game, administration, optional) => {
});
});
+/**
+ * Custom command to check if a partner district exists in the organization list.
+ *
+ * This command performs the following actions:
+ * 1. Locates and clicks on the 'Districts' option in a list.
+ * 2. Verifies if the partner district name (retrieved from Cypress environment variables) exists in a specific `div` element.
+ * 3. Logs a message confirming that the district exists.
+ *
+ * @param {number} [timeout=10000] - Optional timeout for each command, defaulting to 10 seconds.
+ */
+Cypress.Commands.add('checkOrgExists', (org, timeout = 10000) => {
+ // Click on the 'Districts' item in the list
+ cy.get('ul > li', { timeout }).contains(org.tabName, { timeout }).click();
+
+ // Verify the partner district name is present in the div
+ cy.get('div', { timeout }).should('contain.text', Cypress.env('testPartnerDistrictName'), {
+ timeout,
+ });
+
+ // Log the district name exists
+ cy.log(`${Cypress.env('testPartnerDistrictName')} exists.`);
+});
+
/**
* Create a mock store for the user type specified.
* @param {string} userType - The type of user to create a mock store for. One of 'superAdmin', 'partnerAdmin', or 'participant'. Defaults to 'participant'.
diff --git a/cypress/support/helper-functions/roar-syntax/languageOptions.js b/cypress/support/helper-functions/roar-syntax/languageOptions.js
index ed96bec8a..8eee718ce 100644
--- a/cypress/support/helper-functions/roar-syntax/languageOptions.js
+++ b/cypress/support/helper-functions/roar-syntax/languageOptions.js
@@ -1,7 +1,7 @@
export const languageOptions = {
en: {
syntax: {
- gameTab: 'Words and Pictures Game',
+ gameTab: 'ROAR - Syntax',
url: '/game/core-tasks/trog',
},
},
diff --git a/package-lock.json b/package-lock.json
index 7d05d0543..bd90bab97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "roar-dashboard",
- "version": "3.0.0",
+ "version": "3.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "roar-dashboard",
- "version": "3.0.0",
+ "version": "3.0.1",
"dependencies": {
"@bdelab/roam-fluency": "1.11.26",
"@bdelab/roar-firekit": "9.1.0",
@@ -20,7 +20,7 @@
"@bdelab/roav-crowding": "1.1.16",
"@bdelab/roav-mep": "^1.1.21",
"@bdelab/roav-ran": "^1.0.30",
- "@levante-framework/core-tasks": "^1.0.0-beta.18",
+ "@levante-framework/core-tasks": "1.0.0-beta.16",
"@sentry/browser": "^8.0.0",
"@sentry/integrations": "^7.114.0",
"@sentry/vite-plugin": "^2.16.1",
@@ -9085,9 +9085,9 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
},
"node_modules/@levante-framework/core-tasks": {
- "version": "1.0.0-beta.18",
- "resolved": "https://registry.npmjs.org/@levante-framework/core-tasks/-/core-tasks-1.0.0-beta.18.tgz",
- "integrity": "sha512-FRq1heE8PlcYUmfV08r35Ev4DbYN/IJPr6qqd6Qekpfq1rZdy5iXgNzcSsUAkbnB5RhFj12XUKtenpSnuoG0wg==",
+ "version": "1.0.0-beta.16",
+ "resolved": "https://registry.npmjs.org/@levante-framework/core-tasks/-/core-tasks-1.0.0-beta.16.tgz",
+ "integrity": "sha512-11+zouZ7JaWAvwEaf5m7Wa4XSjypPB0uqoS4NcjgsK6NjnNsuO38dcmRoYAILJ8rq317o+I2XwTFaiKz95wU0g==",
"dependencies": {
"@bdelab/jscat": "^3.0.3",
"@bdelab/roar-firekit": "^6.1.2",
@@ -44690,9 +44690,9 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
},
"@levante-framework/core-tasks": {
- "version": "1.0.0-beta.18",
- "resolved": "https://registry.npmjs.org/@levante-framework/core-tasks/-/core-tasks-1.0.0-beta.18.tgz",
- "integrity": "sha512-FRq1heE8PlcYUmfV08r35Ev4DbYN/IJPr6qqd6Qekpfq1rZdy5iXgNzcSsUAkbnB5RhFj12XUKtenpSnuoG0wg==",
+ "version": "1.0.0-beta.16",
+ "resolved": "https://registry.npmjs.org/@levante-framework/core-tasks/-/core-tasks-1.0.0-beta.16.tgz",
+ "integrity": "sha512-11+zouZ7JaWAvwEaf5m7Wa4XSjypPB0uqoS4NcjgsK6NjnNsuO38dcmRoYAILJ8rq317o+I2XwTFaiKz95wU0g==",
"requires": {
"@bdelab/jscat": "^3.0.3",
"@bdelab/roar-firekit": "^6.1.2",
diff --git a/package.json b/package.json
index 93adc8496..843473f00 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "roar-dashboard",
"private": true,
- "version": "3.0.0",
+ "version": "3.0.1",
"type": "module",
"scripts": {
"build": "export VITE_FIREBASE_DATA_SOURCE=live && vite build",
@@ -43,7 +43,7 @@
"@bdelab/roav-crowding": "1.1.16",
"@bdelab/roav-mep": "^1.1.21",
"@bdelab/roav-ran": "^1.0.30",
- "@levante-framework/core-tasks": "^1.0.0-beta.18",
+ "@levante-framework/core-tasks": "1.0.0-beta.16",
"@sentry/browser": "^8.0.0",
"@sentry/integrations": "^7.114.0",
"@sentry/vite-plugin": "^2.16.1",
diff --git a/roar-firebase-functions b/roar-firebase-functions
index 737a604aa..27875477c 160000
--- a/roar-firebase-functions
+++ b/roar-firebase-functions
@@ -1 +1 @@
-Subproject commit 737a604aadcb35d6e4588124b33279dff1d5abe8
+Subproject commit 27875477c98d9b65712b2413b3780ed15bf97e6c
diff --git a/src/components/ListOrgs.vue b/src/components/ListOrgs.vue
index decea127e..a371d5934 100644
--- a/src/components/ListOrgs.vue
+++ b/src/components/ListOrgs.vue
@@ -95,9 +95,16 @@
Code:
-
+
diff --git a/src/components/auth/RegisterStudent.vue b/src/components/auth/RegisterStudent.vue
index 1fcbfddb1..d23fc94c9 100644
--- a/src/components/auth/RegisterStudent.vue
+++ b/src/components/auth/RegisterStudent.vue
@@ -51,7 +51,7 @@
You are registering for:
-
{{ student.orgName }}
+
{{ student.orgName }}
-
+
+
@@ -564,6 +571,28 @@ const getScoresAndSupportFromAssessment = ({
};
};
+const computedProgressData = computed(() => {
+ if (!assignmentData.value) return [];
+ return assignmentData.value.map(({ assignment }) => {
+ const progress = assignment.assessments.reduce((acc, assessment) => {
+ const status = assessment.optional
+ ? 'optional'
+ : assessment.completedOn
+ ? 'completed'
+ : assessment.startedOn
+ ? 'started'
+ : 'assigned';
+
+ acc[assessment.taskId] = { value: status };
+ return acc;
+ }, {});
+ return {
+ userPid: assignment.userData?.assessmentPid, // Assuming user contains a `username` property
+ progress,
+ };
+ });
+});
+
// This function takes in the return from assignmentFetchAll and returns 2 objects
// 1. assignmentTableData: The data that should be passed into the ROARDataTable component
// 2. runsByTaskId: run data for the TaskReport distribution chartsb
@@ -845,43 +874,74 @@ const viewOptions = ref([
{ label: 'Raw Score', value: 'raw' },
]);
-const exportSelected = (selectedRows) => {
- const computedExportData = _map(selectedRows, ({ user, scores }) => {
+/**
+ * Creates and formats the data for exporting user, score, and optionally, progress information to a CSV file.
+ *
+ * This function generates a structured dataset based on user and score data, with optional inclusion of progress
+ * data. It ensures that the data is organized appropriately for export, including task-specific formatting an
+ * reliability checks. If progress data is included, it appends relevant progress information per task.
+ *
+ * This function also checks for the user's role (e.g., super admin) to determine additional fields (such as PID),
+ * handles task-specific score presentation based on configuration, and validates task reliability using engagement
+ * flags. If scores are found unreliable, the reliability reason is included. If the task is incomplete, it is marked as
+ * such.
+ *
+ * @param {Object[]} rows - The array of user data and associated scores.
+ * @param {Object} rows[].user - The user object containing user details such as username, email, first name, and last
+ * name.
+ * @param {Object} rows[].scores - The scores object containing task-related score data for the user. It supports
+ * different score types (percent correct, raw scores, standard scores, etc.) based on task configuration.
+ * @param {boolean} [includeProgress=false] - Flag indicating whether to include task progress data in the export. If
+ * true, progress data will be fetched and appended for each task per user.
+ *
+ * @returns {Array} - The formatted data array, where each object represents a user and their associated scores.
+ * This data is ready for CSV export and optionally includes progress information.
+ */
+
+const createExportData = ({ rows, includeProgress = false }) => {
+ const computedExportData = _map(rows, ({ user, scores }) => {
let tableRow = {
- Username: _get(user, 'username'),
- First: _get(user, 'firstName'),
- Last: _get(user, 'lastName'),
- Grade: _get(user, 'grade'),
+ Username: user?.username,
+ Email: user?.email, // This will only be used when exporting all rows
+ First: user?.firstName,
+ Last: user?.lastName,
+ Grade: user?.grade,
};
+
if (authStore.isUserSuperAdmin) {
- tableRow['PID'] = _get(user, 'assessmentPid');
+ tableRow['PID'] = user?.assessmentPid;
}
+
if (props.orgType === 'district') {
- tableRow['School'] = _get(user, 'schoolName');
+ tableRow['School'] = user?.schoolName;
}
+
for (const taskId in scores) {
const score = scores[taskId];
+ const taskName = tasksDictionary.value[taskId]?.publicName ?? taskId;
+
+ // Add task-specific score information
if (tasksToDisplayPercentCorrect.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Percent Correct`] = score.percentCorrect;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Attempted`] = score.numAttempted;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
+ tableRow[`${taskName} - Percent Correct`] = score.percentCorrect;
+ tableRow[`${taskName} - Num Attempted`] = score.numAttempted;
+ tableRow[`${taskName} - Num Correct`] = score.numCorrect;
} else if (tasksToDisplayCorrectIncorrectDifference.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Correct/Incorrect Difference`] =
- score.correctIncorrectDifference;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Incorrect`] = score.numIncorrect;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
+ tableRow[`${taskName} - Correct/Incorrect Difference`] = score.correctIncorrectDifference;
+ tableRow[`${taskName} - Num Incorrect`] = score.numIncorrect;
+ tableRow[`${taskName} - Num Correct`] = score.numCorrect;
} else if (tasksToDisplayTotalCorrect.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Attempted`] = score.numAttempted;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
+ tableRow[`${taskName} - Num Correct`] = score.numCorrect;
+ tableRow[`${taskName} - Num Attempted`] = score.numAttempted;
} else if (rawOnlyTasks.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Raw`] = score.rawScore;
+ tableRow[`${taskName} - Raw`] = score.rawScore;
} else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Percentile`] = score.percentileString;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Standard`] = score.standardScore;
-
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Raw`] = score.rawScore;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Support Level`] = score.supportLevel;
+ tableRow[`${taskName} - Percentile`] = score.percentileString;
+ tableRow[`${taskName} - Standard`] = score.standardScore;
+ tableRow[`${taskName} - Raw`] = score.rawScore;
+ tableRow[`${taskName} - Support Level`] = score.supportLevel;
}
+
+ // Add reliability information
if (score.reliable !== undefined && !score.reliable && score.engagementFlags !== undefined) {
const engagementFlags = Object.keys(score.engagementFlags);
if (engagementFlags.length > 0) {
@@ -889,106 +949,122 @@ const exportSelected = (selectedRows) => {
const filteredFlags = Object.keys(score.engagementFlags).filter((flag) =>
includedValidityFlags[taskId].includes(flag),
);
- if (filteredFlags.length === 0) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Unreliable';
- } else {
- const engagementFlagString = 'Unreliable: ' + filteredFlags.map((key) => _lowerCase(key)).join(', ');
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = engagementFlagString;
- }
+ tableRow[`${taskName} - Reliability`] =
+ filteredFlags.length === 0 ? 'Unreliable' : `Unreliable: ${filteredFlags.map(_lowerCase).join(', ')}`;
} else {
- const engagementFlagString = 'Unreliable: ' + engagementFlags.map((key) => _lowerCase(key)).join(', ');
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = engagementFlagString;
+ tableRow[`${taskName} - Reliability`] = `Unreliable: ${engagementFlags.map(_lowerCase).join(', ')}`;
}
} else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Assessment Incomplete';
+ tableRow[`${taskName} - Reliability`] = 'Assessment Incomplete';
}
} else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Reliable';
+ tableRow[`${taskName} - Reliability`] = 'Reliable';
}
- }
- return tableRow;
- });
- exportCsv(
- computedExportData,
- `roar-scores-${_kebabCase(getTitle(administrationInfo.value, isSuperAdmin.value))}-${_kebabCase(
- orgInfo.value.name,
- )}-selected.csv`,
- );
- return;
-};
-const exportAll = async () => {
- const computedExportData = _map(computeAssignmentAndRunData.value.assignmentTableData, ({ user, scores }) => {
- let tableRow = {
- Username: _get(user, 'username'),
- Email: _get(user, 'email'),
- First: _get(user, 'firstName'),
- Last: _get(user, 'lastName'),
- Grade: _get(user, 'grade'),
- };
- if (authStore.isUserSuperAdmin) {
- tableRow['PID'] = _get(user, 'assessmentPid');
- }
- if (props.orgType === 'district') {
- tableRow['School'] = _get(user, 'schoolName');
- }
- for (const taskId in scores) {
- const score = scores[taskId];
- if (tasksToDisplayPercentCorrect.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Percent Correct`] = score.percentCorrect;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Attempted`] = score.numAttempted;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
- } else if (tasksToDisplayCorrectIncorrectDifference.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Correct/Incorrect Difference`] =
- score.correctIncorrectDifference;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Incorrect`] = score.numIncorrect;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
- } else if (tasksToDisplayTotalCorrect.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Correct`] = score.numCorrect;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Num Attempted`] = score.numAttempted;
- } else if (rawOnlyTasks.includes(taskId)) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Raw`] = score.rawScore;
- } else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Percentile`] = score.percentileString;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Standard`] = score.standardScore;
+ // Add progress immediately after reliability if includeProgress is true
+ if (includeProgress) {
+ const progressRow = computedProgressData.value.find((progress) => progress.userPid === user?.assessmentPid);
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Raw`] = score.rawScore;
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Support Level`] = score.supportLevel;
- }
+ if (progressRow) {
+ scoreReportColumns.value.forEach((column) => {
+ const { field, header: taskName } = column; // Use taskName from the column header
- if (score.reliable !== undefined && !score.reliable && score.engagementFlags !== undefined) {
- const engagementFlags = Object.keys(score.engagementFlags);
- if (engagementFlags.length > 0) {
- if (includedValidityFlags[taskId]) {
- const filteredFlags = Object.keys(score.engagementFlags).filter((flag) =>
- includedValidityFlags[taskId].includes(flag),
- );
- if (filteredFlags.length === 0) {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Unreliable';
- } else {
- const engagementFlagString = 'Unreliable: ' + filteredFlags.map((key) => _lowerCase(key)).join(', ');
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = engagementFlagString;
+ // Ensure field is defined and is a string before calling startsWith
+ if (typeof field === 'string' && field.startsWith('scores')) {
+ const scoreKey = field.split('.').slice(-2, -1)[0]; // Extract taskId (e.g., "swr", "sre", etc.)
+
+ // Check if taskId exists in progressRow.progress
+ if (progressRow.progress[scoreKey]) {
+ tableRow[`${taskName} - Progress`] = progressRow.progress[scoreKey].value;
+ } else {
+ tableRow[`${taskName} - Progress`] = 'not assigned';
+ }
}
- } else {
- const engagementFlagString = 'Unreliable: ' + engagementFlags.map((key) => _lowerCase(key)).join(', ');
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = engagementFlagString;
- }
+ });
} else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Assessment Incomplete';
+ // If no progressRow is found, mark all scores as "not assigned"
+ scoreReportColumns.value.forEach((column) => {
+ const { field, header: taskName } = column; // Use taskName from the column header
+
+ // Ensure field is defined and is a string before calling startsWith
+ if (field && typeof field === 'string' && field.startsWith('scores')) {
+ tableRow[`${taskName} - Progress`] = 'not assigned';
+ }
+ });
}
- } else {
- tableRow[`${tasksDictionary.value[taskId]?.publicName ?? taskId} - Reliability`] = 'Reliable';
}
}
+
return tableRow;
});
- exportCsv(
- computedExportData,
- `roar-scores-${_kebabCase(getTitle(administrationInfo.value, isSuperAdmin.value))}-${_kebabCase(
- orgInfo.value.name,
- )}.csv`,
+ return computedExportData;
+};
+
+/**
+ * Exports data to a CSV file with dynamic columns based on selected rows and tasks.
+ *
+ * @param {Object} options - Options for exporting data.
+ * @param {Array} options.selectedRows - The selected rows to export. If null, will export all rows.
+ * @param {boolean} options.includeProgress - Determines if progress columns should be included in the export.
+ */
+const exportData = async ({ selectedRows = null, includeProgress = false }) => {
+ const rows = selectedRows || computeAssignmentAndRunData.value.assignmentTableData;
+ let exportData = createExportData({ rows, includeProgress });
+
+ // Analyze all rows to determine which columns are present in the data
+ const allColumns = new Set();
+ exportData.forEach((row) => {
+ Object.keys(row).forEach((column) => {
+ allColumns.add(column);
+ });
+ });
+
+ // Convert Set to Array for sorting
+ const allColumnsArray = Array.from(allColumns);
+
+ // Define the static columns
+ const staticColumns = ['Username', 'Email', 'First', 'Last', 'Grade', 'PID', 'School'];
+
+ // Automatically detect task names by splitting column names and excluding static columns
+ const taskBases = Array.from(
+ new Set(
+ allColumnsArray.filter((col) => !staticColumns.includes(col)).map((col) => col.split(' - ')[1]), // Extract the task name part
+ ),
);
- return;
+
+ // Group task columns and place 'Reliability' and 'Progress' last for each task
+ const finalColumns = [
+ ...staticColumns,
+ ...taskBases.reduce((acc, taskBase) => {
+ const taskCols = allColumnsArray.filter(
+ (col) => col.includes(` - ${taskBase} -`) && !col.endsWith('Reliability') && !col.endsWith('Progress'),
+ );
+ const reliabilityCol = allColumnsArray.filter(
+ (col) => col.includes(` - ${taskBase} -`) && col.endsWith('Reliability'),
+ );
+ const progressCol = allColumnsArray.filter((col) => col.includes(` - ${taskBase} -`) && col.endsWith('Progress'));
+ return [...acc, ...taskCols, ...reliabilityCol, ...progressCol];
+ }, []),
+ ];
+
+ // Reorder exportData according to finalColumns
+ exportData = exportData.map((row) => {
+ const reorderedRow = {};
+ finalColumns.forEach((col) => {
+ reorderedRow[col] = row[col] !== undefined ? row[col] : null;
+ });
+ return reorderedRow;
+ });
+
+ // Create the file name for export
+ const fileNameSuffix = includeProgress ? '-scores-progress' : '-scores';
+ const selectedSuffix = selectedRows ? '-selected' : '';
+ const fileName = `roar${fileNameSuffix}${selectedSuffix}-${_kebabCase(
+ getTitle(administrationInfo.value, isSuperAdmin.value),
+ )}-${_kebabCase(orgInfo.value.name)}.csv`;
+
+ // Export CSV
+ exportCsv(exportData, fileName);
};
function getScoreKeysByRow(row, grade) {
diff --git a/src/translations/en/en-componentTranslations.json b/src/translations/en/en-componentTranslations.json
index 411fda5c4..4dd7cae20 100644
--- a/src/translations/en/en-componentTranslations.json
+++ b/src/translations/en/en-componentTranslations.json
@@ -61,7 +61,7 @@
"theoryOfMindDescription": "Listen to the stories and answer the questions.",
"theoryOfMindName": "Stories Game",
"trogDescription": "Listen to the words and choose the matching picture.",
- "trogName": "Words and Pictures Game",
+ "trogName": "ROAR - Syntax",
"surveyName": "Thoughts and Feelings",
"surveyDescription": "Answer questions about your time at school.",
"mefsName": "Lion and Monkey Game",
diff --git a/src/translations/en/us/en-us-componentTranslations.json b/src/translations/en/us/en-us-componentTranslations.json
index 5dc2b9467..b875b4958 100644
--- a/src/translations/en/us/en-us-componentTranslations.json
+++ b/src/translations/en/us/en-us-componentTranslations.json
@@ -61,7 +61,7 @@
"theoryOfMindDescription": "Listen to the stories and answer the questions.",
"theoryOfMindName": "Stories Game",
"trogDescription": "Listen to the words and choose the matching picture.",
- "trogName": "Words and Pictures Game",
+ "trogName": "ROAR - Syntax",
"surveyName": "Thoughts and Feelings",
"surveyDescription": "Answer questions about your time at school.",
"mefsName": "Lion and Monkey Game",