From 3129fd8f50aae7c5d4fc7a89dd7ecfda3d1af0e1 Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Fri, 19 Sep 2025 14:24:46 -0700 Subject: [PATCH 1/7] start of psi report download --- backend/src/components/psiSelection.js | 71 +++++++++++++++++++ backend/src/config/index.js | 3 + backend/src/routes/psiSelection.js | 12 ++++ .../school/GraduationSchoolTabs.vue | 16 +++++ tools/config/update-configmap.sh | 4 +- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 backend/src/components/psiSelection.js create mode 100644 backend/src/routes/psiSelection.js diff --git a/backend/src/components/psiSelection.js b/backend/src/components/psiSelection.js new file mode 100644 index 000000000..ef2e4b6e7 --- /dev/null +++ b/backend/src/components/psiSelection.js @@ -0,0 +1,71 @@ +'use strict'; +const { + getAccessToken, + getData, + handleExceptionResponse +} = require('../utils'); +const HttpStatus = require('http-status-codes'); +const config = require('../../config'); +const log = require('../logger'); +const {doesSchoolBelongToDistrict} = require('../institute-cache'); + + +async function downloadPsiSelectionReport(req, res) { + try { + console.log(req.params.schoolID); + if(req.session.activeInstituteType === 'DISTRICT'){ + if(!doesSchoolBelongToDistrict(req.params.schoolID, req.session.activeInstituteIdentifier)){ + return res.status(HttpStatus.CONFLICT).json({ + status: HttpStatus.CONFLICT, + message: 'The school is not within your district. The report cannot be accessed.' + }); + } else { + return res.status(HttpStatus.CONFLICT).json({ + status: HttpStatus.CONFLICT, + message: 'This report is only available to schools.' + }); + } + }else if(req.session.activeInstituteType === 'SCHOOL'){ + if(req.params.schoolID !== req.session.activeInstituteIdentifier){ + return res.status(HttpStatus.CONFLICT).json({ + status: HttpStatus.CONFLICT, + message: 'Your school is not your school. The report cannot be accessed.' + }); + } + } + + + const token = getAccessToken(req); + + let url = `${config.get('psiSelection:rootURL')}/report/school/${req.params.schoolID}`; + + const resData = await getData(token, url); + const fileDetails = getFileDetails('psi', null, null); + + setResponseHeaders(res, fileDetails); + const buffer = Buffer.from(resData.documentData, 'base64'); + return res.status(HttpStatus.OK).send(buffer); + } catch (e) { + log.error('downloadPsiReport Error', e.stack); + return handleExceptionResponse(e, res); + } +} + +function setResponseHeaders(res, { filename, contentType }) { + res.setHeader('Content-Disposition', `attachment; filename=${filename}`); + res.setHeader('Content-Type', contentType); +} + + +function getFileDetails(reportType) { + const mappings = { + + 'DEFAULT': { filename: 'download.csv', contentType: 'text/csv' } + }; + return mappings[reportType] || mappings['DEFAULT']; +} + +module.exports = { + downloadPsiSelectionReport +}; + diff --git a/backend/src/config/index.js b/backend/src/config/index.js index 44bf925d5..765b4aeda 100644 --- a/backend/src/config/index.js +++ b/backend/src/config/index.js @@ -174,6 +174,9 @@ nconf.defaults({ assessmentSpecialCaseTypeCodeURL: process.env.ASSESSMENT_API_ENDPOINT+ '/assessment-specialcase-types', assessmentStudentsURL: process.env.ASSESSMENT_API_ENDPOINT+ '/student', }, + psiSelection:{ + rootURL: process.env.PSI_SELECTION_API_ENDPOINT, + }, challengeReports:{ rootURL: process.env.CHALLENGE_REPORTS_API_ENDPOINT } diff --git a/backend/src/routes/psiSelection.js b/backend/src/routes/psiSelection.js new file mode 100644 index 000000000..0043f44c2 --- /dev/null +++ b/backend/src/routes/psiSelection.js @@ -0,0 +1,12 @@ +const express = require('express'); +const router = express.Router(); +const { downloadPsiSelectionReport } = require('../components/psiSelection'); +const auth = require('../components/auth'); +const isValidBackendToken = auth.isValidBackendToken(); +const { isValidUUIDParam, validateAccessToken, checkEdxUserPermission } = require('../components/permissionUtils'); +const { PERMISSION } = require('../util/Permission'); + + +router.get('/psi/reports/school/:schoolID', auth.refreshJWT, isValidBackendToken, validateAccessToken, isValidUUIDParam('schoolID'), checkEdxUserPermission(PERMISSION.EAS_SCH_VIEW), downloadPsiSelectionReport); + +module.exports = router; diff --git a/frontend/src/components/graduation/school/GraduationSchoolTabs.vue b/frontend/src/components/graduation/school/GraduationSchoolTabs.vue index 6a5ee2522..c019debc7 100644 --- a/frontend/src/components/graduation/school/GraduationSchoolTabs.vue +++ b/frontend/src/components/graduation/school/GraduationSchoolTabs.vue @@ -36,6 +36,13 @@ > Current Students in GRAD System + + PSI Selections + @@ -81,6 +88,15 @@ :school-i-d="schoolID" /> + +
+ todo +
+
diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index 3ee82c72c..2ca8c5e04 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -66,7 +66,7 @@ then curl -sX POST "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TKN" \ - -d "{\"clientId\" : \"edx-soam\",\"secret\" : \"$edxServiceClientSecret\", \"name\" : \"EDX SOAM\", \"description\" : \"Connect user from EDX backend to the SOAM\", \"surrogateAuthRequired\" : false, \"enabled\" : true, \"clientAuthenticatorType\" : \"client-secret\", \"redirectUris\" : [ \"http://localhost*\", \"$SERVER_FRONTEND\", \"$SERVER_FRONTEND/logout\", \"$SERVER_FRONTEND/api/auth/callback_idir_silent_sdc\", \"$SERVER_FRONTEND/api/auth/callback_idir\", \"$SERVER_FRONTEND/session-expired\", \"$SERVER_FRONTEND/api/auth/callback_entra\", \"$SERVER_FRONTEND/api/auth/callback_bceid\",\"$SERVER_FRONTEND/api/auth/callback_activate_entra_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_entra_district_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_district_user\", \"$SERVER_FRONTEND/login-error\", \"$SERVER_FRONTEND/api/auth/login_entra\", \"$SERVER_FRONTEND/api/auth/login_bceid\", \"$SERVER_FRONTEND/api/auth/login_idir\", \"$SERVER_FRONTEND/api/auth/login_entra_activate_user\", \"$SERVER_FRONTEND/api/auth/login_bceid_activate_user\", \"$SERVER_FRONTEND/api/auth/login_entra_activate_district_user\", \"$SERVER_FRONTEND/api/auth/login_bceid_activate_district_user\"], \"webOrigins\" : [ ], \"notBefore\" : 0, \"bearerOnly\" : false, \"consentRequired\" : false, \"standardFlowEnabled\" : true, \"implicitFlowEnabled\" : false, \"directAccessGrantsEnabled\" : false, \"serviceAccountsEnabled\" : true, \"publicClient\" : false, \"frontchannelLogout\" : false, \"protocol\" : \"openid-connect\", \"attributes\" : { \"saml.assertion.signature\" : \"false\", \"saml.multivalued.roles\" : \"false\", \"saml.force.post.binding\" : \"false\", \"saml.encrypt\" : \"false\", \"saml.server.signature\" : \"false\", \"saml.server.signature.keyinfo.ext\" : \"false\", \"exclude.session.state.from.auth.response\" : \"false\", \"saml_force_name_id_format\" : \"false\", \"saml.client.signature\" : \"false\", \"tls.client.certificate.bound.access.tokens\" : \"false\", \"saml.authnstatement\" : \"false\", \"display.on.consent.screen\" : \"false\", \"saml.onetimeuse.condition\" : \"false\" }, \"authenticationFlowBindingOverrides\" : { }, \"fullScopeAllowed\" : true, \"nodeReRegistrationTimeout\" : -1, \"protocolMappers\" : [ { \"name\" : \"last_name\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"last_name\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"last_name\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"first_name\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"first_name\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"first_name\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"middle_names\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"middle_names\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"middle_names\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"SOAM Mapper\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-soam-mapper\", \"consentRequired\" : false, \"config\" : {\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"userinfo.token.claim\" : \"true\" } }, { \"name\" : \"Tenant Mapper\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-tenant-mapper\", \"consentRequired\" : false, \"config\" : {\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"userinfo.token.claim\" : \"true\" } }, { \"name\" : \"user_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"user_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"user_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"idir_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"idir_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"idir_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"idir_username\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"idir_username\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"idir_username\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"bceid_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"bceid_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"bceid_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"email_address\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"email_address\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"email_address\",\"jsonType.label\" : \"String\" } } ], \"defaultClientScopes\" : [ \"web-origins\", \"role_list\", \"profile\", \"roles\", \"email\", \"READ_DIGITALID\", \"READ_DIGITALID_CODETABLE\", \"READ_SECURE_EXCHANGE\", \"WRITE_SECURE_EXCHANGE\", \"READ_SECURE_EXCHANGE_CODES\", \"READ_MINISTRY_TEAMS\", \"READ_EDX_USERS\", \"READ_SCHOOL\", \"DELETE_EDX_USER_SCHOOL_ROLE\", \"WRITE_EDX_USER_SCHOOL_ROLE\", \"WRITE_EDX_USER_SCHOOL\", \"VALIDATE_STUDENT_DEMOGRAPHICS\", \"READ_VALIDATION_CODES\", \"DELETE_EDX_USER_SCHOOL\", \"WRITE_EDX_USER_DISTRICT\", \"DELETE_EDX_USER_DISTRICT\", \"DELETE_EDX_USER_DISTRICT_ROLE\", \"GET_NEXT_PEN_NUMBER\", \"WRITE_STUDENT\", \"DELETE_EDX_USER\", \"WRITE_EDX_USER\", \"READ_EDX_USER_SCHOOLS\", \"ACTIVATE_EDX_USER\", \"WRITE_ACTIVATION_CODE\", \"READ_PRIMARY_ACTIVATION_CODE\", \"DISTRICT_USER_ACTIVATION_INVITE_SAGA\", \"SCHOOL_USER_ACTIVATION_INVITE_SAGA\", \"CREATE_SECURE_EXCHANGE_COMMENT_SAGA\", \"READ_SECURE_EXCHANGE_DOCUMENT\", \"WRITE_SECURE_EXCHANGE_DOCUMENT\", \"DELETE_SECURE_EXCHANGE_DOCUMENT\", \"READ_SECURE_EXCHANGE_DOCUMENT_TYPES\", \"READ_SECURE_EXCHANGE_DOCUMENT_REQUIREMENTS\", \"READ_STUDENT\", \"DELETE_SECURE_EXCHANGE_COMMENT\", \"WRITE_SECURE_EXCHANGE_COMMENT\", \"READ_SECURE_EXCHANGE_COMMENT\", \"DELETE_SECURE_EXCHANGE_STUDENT\", \"WRITE_SECURE_EXCHANGE_STUDENT\", \"READ_SECURE_EXCHANGE_STUDENT\", \"READ_DISTRICT\", \"READ_SCHOOL\", \"READ_INSTITUTE_CODES\", \"WRITE_DISTRICT\", \"WRITE_SCHOOL\", \"WRITE_SCHOOL_CONTACT\", \"WRITE_DISTRICT_CONTACT\", \"WRITE_SDC_SCHOOL_COLLECTION\", \"READ_SDC_COLLECTION\", \"WRITE_SDC_COLLECTION\", \"WRITE_PRIMARY_ACTIVATION_CODE\",\"READ_INDEPENDENT_AUTHORITY\", \"READ_SDC_SCHOOL_COLLECTION_STUDENT\", \"READ_COLLECTION_CODES\", \"READ_SCHOOL_CONTACT\", \"READ_DISTRICT_CONTACT\", \"WRITE_SDC_SCHOOL_COLLECTION_STUDENT\", \"DELETE_SDC_SCHOOL_COLLECTION_STUDENT\", \"READ_SDC_DISTRICT_COLLECTION\", \"WRITE_SDC_DISTRICT_COLLECTION\", \"WRITE_GRAD_COLLECTION\", \"READ_GRAD_COLLECTION\", \"READ_ASSESSMENT_SESSIONS\", \"READ_ASSESSMENT_STUDENT\", \"WRITE_ASSESSMENT_STUDENT\", \"READ_FILESET_STUDENT_ERROR\", \"READ_GRAD_COLLECTION_CODES\", \"READ_INCOMING_FILESET\", \"GET_GRADUATION_DATA\", \"GRAD_BUSINESS_R\", \"READ_GRAD_STUDENT_DATA\", \"READ_GRAD_STUDENT_REPORT_DATA\", \"READ_GRAD_STUDENT_CERTIFICATE_DATA\", \"GET_GRADUATION_TRANSCRIPT\", \"GET_GRADUATION_CERTIFICATE\", \"GET_GRADUATION_ACHIEVEMENT\", \"READ_GRAD_GRADUATION_STATUS\", \"CREATE_STUDENT_TRANSCRIPT_REPORT\", \"READ_REPORTING_PERIOD\", \"READ_GRAD_SCHOOL\", \"READ_CHALLENGE_REPORTS\", \"WRITE_CHALLENGE_REPORTS\", \"READ_CHALLENGE_REPORTS_CODES\", \"READ_ASSESSMENT_REPORT\", \"READ_GRAD_STUDENT_REPORT\"], \"optionalClientScopes\" : [ \"address\", \"phone\"], \"access\" : { \"view\" : true, \"configure\" : true, \"manage\" : true }}" + -d "{\"clientId\" : \"edx-soam\",\"secret\" : \"$edxServiceClientSecret\", \"name\" : \"EDX SOAM\", \"description\" : \"Connect user from EDX backend to the SOAM\", \"surrogateAuthRequired\" : false, \"enabled\" : true, \"clientAuthenticatorType\" : \"client-secret\", \"redirectUris\" : [ \"http://localhost*\", \"$SERVER_FRONTEND\", \"$SERVER_FRONTEND/logout\", \"$SERVER_FRONTEND/api/auth/callback_idir_silent_sdc\", \"$SERVER_FRONTEND/api/auth/callback_idir\", \"$SERVER_FRONTEND/session-expired\", \"$SERVER_FRONTEND/api/auth/callback_entra\", \"$SERVER_FRONTEND/api/auth/callback_bceid\",\"$SERVER_FRONTEND/api/auth/callback_activate_entra_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_entra_district_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_user\",\"$SERVER_FRONTEND/api/auth/callback_activate_district_user\", \"$SERVER_FRONTEND/login-error\", \"$SERVER_FRONTEND/api/auth/login_entra\", \"$SERVER_FRONTEND/api/auth/login_bceid\", \"$SERVER_FRONTEND/api/auth/login_idir\", \"$SERVER_FRONTEND/api/auth/login_entra_activate_user\", \"$SERVER_FRONTEND/api/auth/login_bceid_activate_user\", \"$SERVER_FRONTEND/api/auth/login_entra_activate_district_user\", \"$SERVER_FRONTEND/api/auth/login_bceid_activate_district_user\"], \"webOrigins\" : [ ], \"notBefore\" : 0, \"bearerOnly\" : false, \"consentRequired\" : false, \"standardFlowEnabled\" : true, \"implicitFlowEnabled\" : false, \"directAccessGrantsEnabled\" : false, \"serviceAccountsEnabled\" : true, \"publicClient\" : false, \"frontchannelLogout\" : false, \"protocol\" : \"openid-connect\", \"attributes\" : { \"saml.assertion.signature\" : \"false\", \"saml.multivalued.roles\" : \"false\", \"saml.force.post.binding\" : \"false\", \"saml.encrypt\" : \"false\", \"saml.server.signature\" : \"false\", \"saml.server.signature.keyinfo.ext\" : \"false\", \"exclude.session.state.from.auth.response\" : \"false\", \"saml_force_name_id_format\" : \"false\", \"saml.client.signature\" : \"false\", \"tls.client.certificate.bound.access.tokens\" : \"false\", \"saml.authnstatement\" : \"false\", \"display.on.consent.screen\" : \"false\", \"saml.onetimeuse.condition\" : \"false\" }, \"authenticationFlowBindingOverrides\" : { }, \"fullScopeAllowed\" : true, \"nodeReRegistrationTimeout\" : -1, \"protocolMappers\" : [ { \"name\" : \"last_name\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"last_name\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"last_name\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"first_name\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"first_name\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"first_name\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"middle_names\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"middle_names\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"middle_names\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"SOAM Mapper\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-soam-mapper\", \"consentRequired\" : false, \"config\" : {\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"userinfo.token.claim\" : \"true\" } }, { \"name\" : \"Tenant Mapper\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-tenant-mapper\", \"consentRequired\" : false, \"config\" : {\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"userinfo.token.claim\" : \"true\" } }, { \"name\" : \"user_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"user_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"user_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"idir_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"idir_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"idir_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"idir_username\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"idir_username\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"idir_username\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"bceid_guid\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"bceid_guid\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"bceid_guid\",\"jsonType.label\" : \"String\" } }, { \"name\" : \"email_address\", \"protocol\" : \"openid-connect\", \"protocolMapper\" : \"oidc-usermodel-attribute-mapper\", \"consentRequired\" : false, \"config\" : {\"userinfo.token.claim\" : \"true\",\"user.attribute\" : \"email_address\",\"id.token.claim\" : \"true\",\"access.token.claim\" : \"true\",\"claim.name\" : \"email_address\",\"jsonType.label\" : \"String\" } } ], \"defaultClientScopes\" : [ \"web-origins\", \"role_list\", \"profile\", \"roles\", \"email\", \"READ_DIGITALID\", \"READ_DIGITALID_CODETABLE\", \"READ_SECURE_EXCHANGE\", \"WRITE_SECURE_EXCHANGE\", \"READ_SECURE_EXCHANGE_CODES\", \"READ_MINISTRY_TEAMS\", \"READ_EDX_USERS\", \"READ_SCHOOL\", \"DELETE_EDX_USER_SCHOOL_ROLE\", \"WRITE_EDX_USER_SCHOOL_ROLE\", \"WRITE_EDX_USER_SCHOOL\", \"VALIDATE_STUDENT_DEMOGRAPHICS\", \"READ_VALIDATION_CODES\", \"DELETE_EDX_USER_SCHOOL\", \"WRITE_EDX_USER_DISTRICT\", \"DELETE_EDX_USER_DISTRICT\", \"DELETE_EDX_USER_DISTRICT_ROLE\", \"GET_NEXT_PEN_NUMBER\", \"WRITE_STUDENT\", \"DELETE_EDX_USER\", \"WRITE_EDX_USER\", \"READ_EDX_USER_SCHOOLS\", \"ACTIVATE_EDX_USER\", \"WRITE_ACTIVATION_CODE\", \"READ_PRIMARY_ACTIVATION_CODE\", \"DISTRICT_USER_ACTIVATION_INVITE_SAGA\", \"SCHOOL_USER_ACTIVATION_INVITE_SAGA\", \"CREATE_SECURE_EXCHANGE_COMMENT_SAGA\", \"READ_SECURE_EXCHANGE_DOCUMENT\", \"WRITE_SECURE_EXCHANGE_DOCUMENT\", \"DELETE_SECURE_EXCHANGE_DOCUMENT\", \"READ_SECURE_EXCHANGE_DOCUMENT_TYPES\", \"READ_SECURE_EXCHANGE_DOCUMENT_REQUIREMENTS\", \"READ_STUDENT\", \"DELETE_SECURE_EXCHANGE_COMMENT\", \"WRITE_SECURE_EXCHANGE_COMMENT\", \"READ_SECURE_EXCHANGE_COMMENT\", \"DELETE_SECURE_EXCHANGE_STUDENT\", \"WRITE_SECURE_EXCHANGE_STUDENT\", \"READ_SECURE_EXCHANGE_STUDENT\", \"READ_DISTRICT\", \"READ_SCHOOL\", \"READ_INSTITUTE_CODES\", \"WRITE_DISTRICT\", \"WRITE_SCHOOL\", \"WRITE_SCHOOL_CONTACT\", \"WRITE_DISTRICT_CONTACT\", \"WRITE_SDC_SCHOOL_COLLECTION\", \"READ_SDC_COLLECTION\", \"WRITE_SDC_COLLECTION\", \"WRITE_PRIMARY_ACTIVATION_CODE\",\"READ_INDEPENDENT_AUTHORITY\", \"READ_SDC_SCHOOL_COLLECTION_STUDENT\", \"READ_COLLECTION_CODES\", \"READ_SCHOOL_CONTACT\", \"READ_DISTRICT_CONTACT\", \"WRITE_SDC_SCHOOL_COLLECTION_STUDENT\", \"DELETE_SDC_SCHOOL_COLLECTION_STUDENT\", \"READ_SDC_DISTRICT_COLLECTION\", \"WRITE_SDC_DISTRICT_COLLECTION\", \"WRITE_GRAD_COLLECTION\", \"READ_GRAD_COLLECTION\", \"READ_ASSESSMENT_SESSIONS\", \"READ_ASSESSMENT_STUDENT\", \"WRITE_ASSESSMENT_STUDENT\", \"READ_FILESET_STUDENT_ERROR\", \"READ_GRAD_COLLECTION_CODES\", \"READ_INCOMING_FILESET\", \"GET_GRADUATION_DATA\", \"GRAD_BUSINESS_R\", \"READ_GRAD_STUDENT_DATA\", \"READ_GRAD_STUDENT_REPORT_DATA\", \"READ_GRAD_STUDENT_CERTIFICATE_DATA\", \"GET_GRADUATION_TRANSCRIPT\", \"GET_GRADUATION_CERTIFICATE\", \"GET_GRADUATION_ACHIEVEMENT\", \"READ_GRAD_GRADUATION_STATUS\", \"CREATE_STUDENT_TRANSCRIPT_REPORT\", \"READ_REPORTING_PERIOD\", \"READ_GRAD_SCHOOL\", \"READ_CHALLENGE_REPORTS\", \"WRITE_CHALLENGE_REPORTS\", \"READ_CHALLENGE_REPORTS_CODES\", \"READ_ASSESSMENT_REPORT\", \"READ_GRAD_STUDENT_REPORT\" , \"READ_PSI_SELECTION\"], \"optionalClientScopes\" : [ \"address\", \"phone\"], \"access\" : { \"view\" : true, \"configure\" : true, \"manage\" : true }}" else echo echo Creating client edx-soam without secret @@ -145,7 +145,7 @@ echo Removing key files rm tempPenBackendkey rm tempPenBackendkey.pub echo Creating config map $APP_NAME-backend-config-map -oc create -n $OPENSHIFT_NAMESPACE-$envValue configmap $APP_NAME-backend-config-map --from-literal=IS_RATE_LIMIT_ENABLED="$IS_RATE_LIMIT_ENABLED" --from-literal=RATE_LIMIT_WINDOW_IN_SEC="$RATE_LIMIT_WINDOW_IN_SEC" --from-literal=RATE_LIMIT_LIMIT="$RATE_LIMIT_LIMIT" --from-literal=WEB_SERVER_FRONTEND="$WEB_SERVER_FRONTEND" --from-literal=WEB_SOCKET_URL="$WEB_SOCKET_URL" --from-literal=HELP=$helpURL --from-literal=TERMS_OF_USE=$termsOfUseURL --from-literal=TZ=$TZVALUE --from-literal=CLAMAV_HOST="clamav.$COMMON_NAMESPACE-$envValue.svc.cluster.local" --from-literal=CLAMAV_PORT="3310" --from-literal=BANNER_COLOR=$bannerColor --from-literal=BANNER_ENVIRONMENT=$bannerEnvironment --from-literal=UI_PRIVATE_KEY="$UI_PRIVATE_KEY_VAL" --from-literal=UI_PUBLIC_KEY="$UI_PUBLIC_KEY_VAL" --from-literal=SOAM_CLIENT_ID=$APP_NAME-soam --from-literal=SOAM_CLIENT_SECRET=$edxServiceClientSecret --from-literal=SERVER_FRONTEND="$SERVER_FRONTEND" --from-literal=ISSUER=EDX_Application --from-literal=EDX_API_ENDPOINT="http://edx-api-master.$OPENSHIFT_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/edx" --from-literal=SOAM_PUBLIC_KEY="$formattedPublicKey" --from-literal=SOAM_DISCOVERY=https://$SOAM_KC/auth/realms/$SOAM_KC_REALM_ID/.well-known/openid-configuration --from-literal=SOAM_URL=https://$SOAM_KC --from-literal=STUDENT_API_ENDPOINT="http://student-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/student" --from-literal=DIGITALID_API_ENDPOINT="http://digitalid-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/digital-id" --from-literal=PEN_SERVICES_API_URL="http://pen-services-api-master.$PEN_NAMESPACE.svc.cluster.local:8080/api/v1/pen-services" --from-literal=INSTITUTE_API_ENDPOINT="http://institute-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/institute" --from-literal=GRAD_SCHOOL_API_ENDPOINT="http://grad-school-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/grad-school" --from-literal=GRAD_API_ENDPOINT="http://grad-data-collection-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/grad-data-collection" --from-literal=GRAD_STUDENT_API_ENDPOINT="http://educ-grad-student-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v1/student" --from-literal=SDC_API_ENDPOINT="http://student-data-collection-api-master.$OPENSHIFT_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/student-data-collection" --from-literal=ASSESSMENT_API_ENDPOINT="http://student-assessment-api-master.$ASSESSMENT_NAMESPACE.svc.cluster.local:8080/api/v1/student-assessment" --from-literal=GRAD_REPORTS_API_ENDPOINT="http://educ-grad-business-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v1" --from-literal=GRAD_SUMMARY_REPORTS_API_ENDPOINT="http://educ-grad-business-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v2" --from-literal=CHALLENGE_REPORTS_API_ENDPOINT="http://challenge-reports-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/challenge-reports" --from-literal=EMAIL_SECRET_KEY="$JWT_SECRET_KEY" --from-literal=SITEMINDER_LOGOUT_ENDPOINT="$siteMinderLogoutUrl" --from-literal=LOG_LEVEL=info --from-literal=REDIS_HOST=redis --from-literal=REDIS_PORT=6379 --from-literal=TOKEN_TTL_MINUTES=1440 --from-literal=NATS_URL="$NATS_URL" --from-literal=NATS_CLUSTER="$NATS_CLUSTER" --from-literal=SCHEDULER_CRON_STALE_SAGA_RECORD_REDIS="0 0/5 * * * *" --from-literal=MIN_TIME_BEFORE_SAGA_IS_STALE_IN_MINUTES=5 --from-literal=NODE_ENV="openshift" --from-literal=DISABLE_SDC_FUNCTIONALITY="$disableSdcFunctionality" --from-literal=DISABLE_GRAD_FUNCTIONALITY="$disableGradFunctionality" --from-literal=DISABLE_ASSESSMENT_FUNCTIONALITY="$disableAssessmentFunctionality" --from-literal=SLD_MIGRATION_DATE="$sldMigrationDate" --dry-run -o yaml | oc apply -f - +oc create -n $OPENSHIFT_NAMESPACE-$envValue configmap $APP_NAME-backend-config-map --from-literal=IS_RATE_LIMIT_ENABLED="$IS_RATE_LIMIT_ENABLED" --from-literal=RATE_LIMIT_WINDOW_IN_SEC="$RATE_LIMIT_WINDOW_IN_SEC" --from-literal=RATE_LIMIT_LIMIT="$RATE_LIMIT_LIMIT" --from-literal=WEB_SERVER_FRONTEND="$WEB_SERVER_FRONTEND" --from-literal=WEB_SOCKET_URL="$WEB_SOCKET_URL" --from-literal=HELP=$helpURL --from-literal=TERMS_OF_USE=$termsOfUseURL --from-literal=TZ=$TZVALUE --from-literal=CLAMAV_HOST="clamav.$COMMON_NAMESPACE-$envValue.svc.cluster.local" --from-literal=CLAMAV_PORT="3310" --from-literal=BANNER_COLOR=$bannerColor --from-literal=BANNER_ENVIRONMENT=$bannerEnvironment --from-literal=UI_PRIVATE_KEY="$UI_PRIVATE_KEY_VAL" --from-literal=UI_PUBLIC_KEY="$UI_PUBLIC_KEY_VAL" --from-literal=SOAM_CLIENT_ID=$APP_NAME-soam --from-literal=SOAM_CLIENT_SECRET=$edxServiceClientSecret --from-literal=SERVER_FRONTEND="$SERVER_FRONTEND" --from-literal=ISSUER=EDX_Application --from-literal=EDX_API_ENDPOINT="http://edx-api-master.$OPENSHIFT_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/edx" --from-literal=SOAM_PUBLIC_KEY="$formattedPublicKey" --from-literal=SOAM_DISCOVERY=https://$SOAM_KC/auth/realms/$SOAM_KC_REALM_ID/.well-known/openid-configuration --from-literal=SOAM_URL=https://$SOAM_KC --from-literal=STUDENT_API_ENDPOINT="http://student-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/student" --from-literal=DIGITALID_API_ENDPOINT="http://digitalid-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/digital-id" --from-literal=PEN_SERVICES_API_URL="http://pen-services-api-master.$PEN_NAMESPACE.svc.cluster.local:8080/api/v1/pen-services" --from-literal=INSTITUTE_API_ENDPOINT="http://institute-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/institute" --from-literal=GRAD_SCHOOL_API_ENDPOINT="http://grad-school-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/grad-school" --from-literal=GRAD_API_ENDPOINT="http://grad-data-collection-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/grad-data-collection" --from-literal=GRAD_STUDENT_API_ENDPOINT="http://educ-grad-student-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v1/student" --from-literal=SDC_API_ENDPOINT="http://student-data-collection-api-master.$OPENSHIFT_NAMESPACE-$envValue.svc.cluster.local:8080/api/v1/student-data-collection" --from-literal=ASSESSMENT_API_ENDPOINT="http://student-assessment-api-master.$ASSESSMENT_NAMESPACE.svc.cluster.local:8080/api/v1/student-assessment" --from-literal=PSI_SELECTION_API_ENDPOINT="http://psi-selection-api-master.$ASSESSMENT_NAMESPACE.svc.cluster.local:8080/api/v1/psi-selection" --from-literal=GRAD_REPORTS_API_ENDPOINT="http://educ-grad-business-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v1" --from-literal=GRAD_SUMMARY_REPORTS_API_ENDPOINT="http://educ-grad-business-api.$GRAD_REPORTS_NAMESPACE.svc.cluster.local:8080/api/v2" --from-literal=CHALLENGE_REPORTS_API_ENDPOINT="http://challenge-reports-api-master.$GRAD_DATA_COLLECTION_NAMESPACE.svc.cluster.local:8080/api/v1/challenge-reports" --from-literal=EMAIL_SECRET_KEY="$JWT_SECRET_KEY" --from-literal=SITEMINDER_LOGOUT_ENDPOINT="$siteMinderLogoutUrl" --from-literal=LOG_LEVEL=info --from-literal=REDIS_HOST=redis --from-literal=REDIS_PORT=6379 --from-literal=TOKEN_TTL_MINUTES=1440 --from-literal=NATS_URL="$NATS_URL" --from-literal=NATS_CLUSTER="$NATS_CLUSTER" --from-literal=SCHEDULER_CRON_STALE_SAGA_RECORD_REDIS="0 0/5 * * * *" --from-literal=MIN_TIME_BEFORE_SAGA_IS_STALE_IN_MINUTES=5 --from-literal=NODE_ENV="openshift" --from-literal=DISABLE_SDC_FUNCTIONALITY="$disableSdcFunctionality" --from-literal=DISABLE_GRAD_FUNCTIONALITY="$disableGradFunctionality" --from-literal=DISABLE_ASSESSMENT_FUNCTIONALITY="$disableAssessmentFunctionality" --from-literal=SLD_MIGRATION_DATE="$sldMigrationDate" --dry-run -o yaml | oc apply -f - echo echo Setting environment variables for $APP_NAME-backend-$SOAM_KC_REALM_ID application oc -n $OPENSHIFT_NAMESPACE-$envValue set env --from=configmap/$APP_NAME-backend-config-map deployment/$APP_NAME-backend-$SOAM_KC_REALM_ID From 049d24bddb750b7302fd6512331de6b4be1229d7 Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Fri, 19 Sep 2025 14:46:17 -0700 Subject: [PATCH 2/7] navbar fix --- frontend/src/components/util/NavBar.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/components/util/NavBar.vue b/frontend/src/components/util/NavBar.vue index d77802ab5..152c85737 100644 --- a/frontend/src/components/util/NavBar.vue +++ b/frontend/src/components/util/NavBar.vue @@ -354,6 +354,10 @@ export default { return title.replace(/\s+/g, ''); }, isYukon() { + let isDistrict = this.userInfo.activeInstituteType === 'DISTRICT'; + if (!isDistrict) { + return false; + } let districtID = this.userInfo.activeInstituteIdentifier; let belongsToDistrict = this.activeDistrictsMap.get(districtID); return belongsToDistrict !== null && belongsToDistrict.districtNumber === '098'; From 97826eb96a46d4c3cb21a01aca1cc12367b29989 Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Fri, 19 Sep 2025 14:55:03 -0700 Subject: [PATCH 3/7] psi selections view scaffolding --- .../school/GraduationSchoolTabs.vue | 12 +-- .../school/reports/PsiSelection.vue | 75 +++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/graduation/school/reports/PsiSelection.vue diff --git a/frontend/src/components/graduation/school/GraduationSchoolTabs.vue b/frontend/src/components/graduation/school/GraduationSchoolTabs.vue index c019debc7..9779ba35e 100644 --- a/frontend/src/components/graduation/school/GraduationSchoolTabs.vue +++ b/frontend/src/components/graduation/school/GraduationSchoolTabs.vue @@ -93,9 +93,9 @@ transition="false" reverse-transition="false" > -
- todo -
+ @@ -112,6 +112,7 @@ import { mapState } from 'pinia'; import {ApiRoutes, PAGE_TITLES} from '../../../utils/constants'; import GradSchoolUploadDataComponent from './upload/GradSchoolUploadDataComponent.vue'; import GradReportsAndTranscripts from './reports/GradSchoolReportsAndTranscripts.vue'; +import PsiSelections from './reports/PsiSelection.vue'; import GradSchoolStudentSearch from './students/GradSchoolStudentSearch.vue'; import GradSchoolCurrentStudents from './students/GradSchoolCurrentStudents.vue'; import ApiService from '../../../common/apiService'; @@ -123,7 +124,8 @@ export default { GradSchoolCurrentStudents, GradSchoolStudentSearch, GradReportsAndTranscripts, - GradSchoolUploadDataComponent + GradSchoolUploadDataComponent, + PsiSelections }, mixins: [alertMixin], props: { @@ -135,7 +137,7 @@ export default { }, data() { return { - validTabs: ['uploadData', 'studentSearch', 'gradReports', 'currentStudents'], + validTabs: ['uploadData', 'studentSearch', 'gradReports', 'currentStudents', 'psiSelections'], PAGE_TITLES: PAGE_TITLES, collectionObject: null }; diff --git a/frontend/src/components/graduation/school/reports/PsiSelection.vue b/frontend/src/components/graduation/school/reports/PsiSelection.vue new file mode 100644 index 000000000..29e459bf8 --- /dev/null +++ b/frontend/src/components/graduation/school/reports/PsiSelection.vue @@ -0,0 +1,75 @@ + + + + + From 6a8a1c32b9e89dbb3e4dde3bfe5a69b26d6300fb Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Fri, 19 Sep 2025 15:35:16 -0700 Subject: [PATCH 4/7] psi selections view scaffolding --- backend/src/app.js | 2 ++ backend/src/components/psiSelection.js | 8 ++--- backend/src/routes/psiSelection.js | 2 +- .../school/reports/PsiSelection.vue | 32 +++++++++++++++++-- frontend/src/utils/constants.js | 5 +++ 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/backend/src/app.js b/backend/src/app.js index e2a62775e..de718dfc2 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -36,6 +36,7 @@ const gradRouter = require('./routes/grad'); const gradReportsRouter = require('./routes/gradReports'); const challengeReportsRouter = require('./routes/challengeReports'); const assessmentRouter = require('./routes/assessments'); +const psiRouter = require('./routes/psiSelection'); const promMid = require('express-prometheus-middleware'); const messagePubSub = require('./messaging/message-pub-sub'); messagePubSub.init().then(() => { @@ -223,6 +224,7 @@ apiRouter.use('/grad',gradRouter); apiRouter.use('/gradReports', gradReportsRouter); apiRouter.use('/challengeReports', challengeReportsRouter); apiRouter.use('/assessments',assessmentRouter); +apiRouter.use('/psi', psiRouter); //Handle 500 error app.use((err, _req, res, next) => { diff --git a/backend/src/components/psiSelection.js b/backend/src/components/psiSelection.js index ef2e4b6e7..dd23c203d 100644 --- a/backend/src/components/psiSelection.js +++ b/backend/src/components/psiSelection.js @@ -3,11 +3,11 @@ const { getAccessToken, getData, handleExceptionResponse -} = require('../utils'); +} = require('./utils'); const HttpStatus = require('http-status-codes'); -const config = require('../../config'); -const log = require('../logger'); -const {doesSchoolBelongToDistrict} = require('../institute-cache'); +const config = require('../config'); +const log = require('./logger'); +const {doesSchoolBelongToDistrict} = require('./institute-cache'); async function downloadPsiSelectionReport(req, res) { diff --git a/backend/src/routes/psiSelection.js b/backend/src/routes/psiSelection.js index 0043f44c2..f8f3f00e7 100644 --- a/backend/src/routes/psiSelection.js +++ b/backend/src/routes/psiSelection.js @@ -7,6 +7,6 @@ const { isValidUUIDParam, validateAccessToken, checkEdxUserPermission } = requir const { PERMISSION } = require('../util/Permission'); -router.get('/psi/reports/school/:schoolID', auth.refreshJWT, isValidBackendToken, validateAccessToken, isValidUUIDParam('schoolID'), checkEdxUserPermission(PERMISSION.EAS_SCH_VIEW), downloadPsiSelectionReport); +router.get('/psi/report/school/:schoolID', auth.refreshJWT, isValidBackendToken, validateAccessToken, isValidUUIDParam('schoolID'), checkEdxUserPermission(PERMISSION.EAS_SCH_VIEW), downloadPsiSelectionReport); module.exports = router; diff --git a/frontend/src/components/graduation/school/reports/PsiSelection.vue b/frontend/src/components/graduation/school/reports/PsiSelection.vue index 29e459bf8..b0f162e7e 100644 --- a/frontend/src/components/graduation/school/reports/PsiSelection.vue +++ b/frontend/src/components/graduation/school/reports/PsiSelection.vue @@ -1,19 +1,24 @@ diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 773ff448d..8c62a48ac 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -14,6 +14,7 @@ const studentRoot = baseRoot + '/students'; const penServicesRoot = baseRoot + '/penServices'; const assessmentsRoot = baseRoot + '/assessments'; const challengeReportsRoot = baseRoot + '/challengeReports'; +const psiSelectionRoot = baseRoot + '/psi'; let object; @@ -145,6 +146,10 @@ export const ApiRoutes = Object.freeze({ }, challengeReports: { BASE_URL: challengeReportsRoot + }, + psiSelection: { + BASE_URL: psiSelectionRoot, + REPORT: psiSelectionRoot + '/report', } }); From d992f548da3c98abce158edcdc574f6af79671f3 Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Tue, 23 Sep 2025 10:18:29 -0700 Subject: [PATCH 5/7] correct backend routing --- backend/src/routes/psiSelection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/psiSelection.js b/backend/src/routes/psiSelection.js index f8f3f00e7..11c75c2fd 100644 --- a/backend/src/routes/psiSelection.js +++ b/backend/src/routes/psiSelection.js @@ -7,6 +7,6 @@ const { isValidUUIDParam, validateAccessToken, checkEdxUserPermission } = requir const { PERMISSION } = require('../util/Permission'); -router.get('/psi/report/school/:schoolID', auth.refreshJWT, isValidBackendToken, validateAccessToken, isValidUUIDParam('schoolID'), checkEdxUserPermission(PERMISSION.EAS_SCH_VIEW), downloadPsiSelectionReport); +router.get('/report/school/:schoolID', auth.refreshJWT, isValidBackendToken, validateAccessToken, isValidUUIDParam('schoolID'), checkEdxUserPermission(PERMISSION.EAS_SCH_VIEW), downloadPsiSelectionReport); module.exports = router; From de2ea25a34445edef85c95c0c25ca342a20ade9d Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Tue, 23 Sep 2025 10:25:11 -0700 Subject: [PATCH 6/7] download --- .../graduation/school/reports/PsiSelection.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/graduation/school/reports/PsiSelection.vue b/frontend/src/components/graduation/school/reports/PsiSelection.vue index b0f162e7e..9b493b670 100644 --- a/frontend/src/components/graduation/school/reports/PsiSelection.vue +++ b/frontend/src/components/graduation/school/reports/PsiSelection.vue @@ -44,10 +44,21 @@ export default { const url = `${ApiRoutes.psiSelection.REPORT}/school/${this.schoolID}`; try { - return await ApiService.apiAxios.get(url, { + const res = await ApiService.apiAxios.get(url, { responseType: 'blob' }); + const filename = 'psi-selection.csv'; + + const urlObj = window.URL.createObjectURL(res.data); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = urlObj; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(urlObj); } catch (error) { console.error('Error downloading file:', error); let errorMsg; From 5826d48ef3401805670a20b76f79434abd065e21 Mon Sep 17 00:00:00 2001 From: alexmcdermid Date: Tue, 23 Sep 2025 10:38:51 -0700 Subject: [PATCH 7/7] report name from backend --- backend/src/components/psiSelection.js | 22 +++++++++---------- .../school/reports/PsiSelection.vue | 7 +++++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/backend/src/components/psiSelection.js b/backend/src/components/psiSelection.js index dd23c203d..5a5a0c561 100644 --- a/backend/src/components/psiSelection.js +++ b/backend/src/components/psiSelection.js @@ -33,16 +33,15 @@ async function downloadPsiSelectionReport(req, res) { }); } } - const token = getAccessToken(req); let url = `${config.get('psiSelection:rootURL')}/report/school/${req.params.schoolID}`; const resData = await getData(token, url); - const fileDetails = getFileDetails('psi', null, null); - + const fileDetails = getFileDetails('psi', resData?.reportName); setResponseHeaders(res, fileDetails); + const buffer = Buffer.from(resData.documentData, 'base64'); return res.status(HttpStatus.OK).send(buffer); } catch (e) { @@ -52,17 +51,18 @@ async function downloadPsiSelectionReport(req, res) { } function setResponseHeaders(res, { filename, contentType }) { - res.setHeader('Content-Disposition', `attachment; filename=${filename}`); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.setHeader('Content-Type', contentType); + res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition, Content-Type'); } - -function getFileDetails(reportType) { - const mappings = { - - 'DEFAULT': { filename: 'download.csv', contentType: 'text/csv' } - }; - return mappings[reportType] || mappings['DEFAULT']; +function getFileDetails(reportType, reportName) { + if (reportType === 'psi') { + const base = (reportName || 'psi-selection-report').toString(); + const safe = `${base}.csv`.replace(/[^\w .-]/g, '_'); + return { filename: safe, contentType: 'text/csv; charset=utf-8' }; + } + return { filename: 'download.csv', contentType: 'text/csv; charset=utf-8' }; } module.exports = { diff --git a/frontend/src/components/graduation/school/reports/PsiSelection.vue b/frontend/src/components/graduation/school/reports/PsiSelection.vue index 9b493b670..31bfac514 100644 --- a/frontend/src/components/graduation/school/reports/PsiSelection.vue +++ b/frontend/src/components/graduation/school/reports/PsiSelection.vue @@ -48,7 +48,12 @@ export default { responseType: 'blob' }); - const filename = 'psi-selection.csv'; + let filename = 'PSI Selection Report.csv'; + const dispo = res.headers?.['content-disposition'] || res.headers?.['Content-Disposition']; + if (dispo) { + const m = dispo.match(/filename\*?=(?:UTF-8''|")(.*?)(?:"|;|$)/i); + if (m?.[1]) filename = decodeURIComponent(m[1]); + } const urlObj = window.URL.createObjectURL(res.data); const a = document.createElement('a');