From 26789bf9c36877a1a6f335db035336452d61d27c Mon Sep 17 00:00:00 2001 From: srartese Date: Wed, 4 Oct 2023 15:33:18 -0700 Subject: [PATCH 1/3] Added logic to send unsupported audio filetypes to the api point to convert to mp3 --- .../components/submissions/submissionUtils.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/jsapp/js/components/submissions/submissionUtils.ts b/jsapp/js/components/submissions/submissionUtils.ts index 3de5fd18f2..29d73b5bbd 100644 --- a/jsapp/js/components/submissions/submissionUtils.ts +++ b/jsapp/js/components/submissions/submissionUtils.ts @@ -15,6 +15,7 @@ import { GROUP_TYPES_BEGIN, QUESTION_TYPES, CHOICE_LISTS, + ROOT_URL } from 'js/constants'; import type {AnyRowTypeName} from 'js/constants'; import type { @@ -531,11 +532,35 @@ export function getMediaAttachment( ); submission._attachments.forEach((attachment) => { + if (attachment.filename.includes(validFileName)) { - mediaAttachment = attachment; + // Check if the audio filetype is of type not supported by player and send it to format to mp3 + if(attachment.mimetype.includes('audio/') + && !attachment.mimetype.includes('/mp3') + && !attachment.mimetype.includes('mpeg') + && !attachment.mimetype.includes('/wav') + && !attachment.mimetype.includes('ogg') + ) + { + const lastItem = attachment.filename.split("/").pop(); + const questionPath = Object.keys(submission).find(key => submission[key] === lastItem); + + const newAudioURL = + `${ROOT_URL}/api/v2/assets/${submission._xform_id_string}/data/${attachment.instance}/attachments/?xpath=${questionPath}&format=mp3`; + const newAttachment = { + ...attachment, + download_url: newAudioURL, + download_large_url: newAudioURL, + download_medium_url: newAudioURL, + download_small_url: newAudioURL, + mimetype: 'audio/mp3', + } + mediaAttachment = newAttachment; + } else { + mediaAttachment = attachment; + } } }); - return mediaAttachment; } From 8b03952495d1102400af24d780a46687c8a05341 Mon Sep 17 00:00:00 2001 From: srartese Date: Tue, 17 Oct 2023 16:54:44 -0700 Subject: [PATCH 2/3] Formatted with Prettier and added solve for filenames not matching and being undefiend --- .../components/submissions/submissionUtils.ts | 147 ++++++++++-------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/jsapp/js/components/submissions/submissionUtils.ts b/jsapp/js/components/submissions/submissionUtils.ts index e4425f9ccb..1bfff083fb 100644 --- a/jsapp/js/components/submissions/submissionUtils.ts +++ b/jsapp/js/components/submissions/submissionUtils.ts @@ -15,7 +15,7 @@ import { GROUP_TYPES_BEGIN, QUESTION_TYPES, CHOICE_LISTS, - ROOT_URL + ROOT_URL, } from 'js/constants'; import type {AnyRowTypeName} from 'js/constants'; import type { @@ -55,7 +55,7 @@ export class DisplayGroup { /** Unique identifier */ public name: string | null = null; /** List of groups and responses */ - public children: Array = []; + public children: Array = []; constructor( type: DisplayGroupTypeName, @@ -71,7 +71,7 @@ export class DisplayGroup { } } - addChild(child: DisplayResponse|DisplayGroup) { + addChild(child: DisplayResponse | DisplayGroup) { this.children.push(child); } } @@ -113,7 +113,9 @@ export class DisplayResponse { /** * Returns a sorted object of transcript/translation keys */ -export function sortAnalysisFormJsonKeys(additionalFields: {source: string, dtpath: string}[]) { +export function sortAnalysisFormJsonKeys( + additionalFields: {source: string; dtpath: string}[] +) { let sortedBySource: {[key: string]: string[]} = {}; additionalFields?.forEach((afParams) => { @@ -138,8 +140,8 @@ export function getSubmissionDisplayData( // let's start with a root of survey being a group with special flag const output = new DisplayGroup(DISPLAY_GROUP_TYPES.group_root); - const survey = asset?.content?.survey || [] - const choices = asset?.content?.choices || [] + const survey = asset?.content?.survey || []; + const choices = asset?.content?.choices || []; const flatPaths = getSurveyFlatPaths(survey, true); @@ -219,7 +221,7 @@ export function getSubmissionDisplayData( let matrixGroupObj = new DisplayGroup( DISPLAY_GROUP_TYPES.group_matrix, rowLabel, - rowName, + rowName ); parentGroup.addChild(matrixGroupObj); @@ -255,7 +257,7 @@ export function getSubmissionDisplayData( let rowObj = new DisplayGroup( DISPLAY_GROUP_TYPES.group_regular, rowLabel, - rowName, + rowName ); parentGroup.addChild(rowObj); /* @@ -276,8 +278,8 @@ export function getSubmissionDisplayData( // score and rank don't have list name on them and they need to use // the one of their parent if (row.type === SCORE_ROW_TYPE || row.type === RANK_LEVEL_TYPE) { - const parentGroupRow = survey.find((row) => - getRowName(row) === parentGroup.name + const parentGroupRow = survey.find( + (row) => getRowName(row) === parentGroup.name ); rowListName = getRowListName(parentGroupRow); } @@ -301,14 +303,15 @@ export function getSubmissionDisplayData( let rowqpath = flatPaths[rowName].replace(/\//g, '-'); supplementalDetailKeys[rowqpath]?.forEach((sdKey: string) => { parentGroup.addChild( - new DisplayResponse(null, + new DisplayResponse( + null, getColumnLabel(asset, sdKey, false), sdKey, undefined, - getSupplementalDetailsContent(submissionData, sdKey), + getSupplementalDetailsContent(submissionData, sdKey) ) ); - }) + }); } } } @@ -337,15 +340,19 @@ function populateMatrixData( // This should not happen, as the only DisplayGroup with null name will be of // the group_root type, but we need this for the types. if (matrixGroup.name === null) { - return + return; } // create row display group and add it to matrix group - const matrixRowLabel = getTranslatedRowLabel(matrixRowName, choices, translationIndex); + const matrixRowLabel = getTranslatedRowLabel( + matrixRowName, + choices, + translationIndex + ); let matrixRowGroupObj = new DisplayGroup( DISPLAY_GROUP_TYPES.group_matrix_row, matrixRowLabel, - matrixRowName, + matrixRowName ); matrixGroup.addChild(matrixRowGroupObj); @@ -359,8 +366,8 @@ function populateMatrixData( */ Object.keys(flatPaths).forEach((questionName) => { if (flatPaths[questionName].startsWith(`${matrixGroupPath}/`)) { - const questionSurveyObj = survey.find((row) => - getRowName(row) === questionName + const questionSurveyObj = survey.find( + (row) => getRowName(row) === questionName ); // We are only interested in going further if object was found. if (typeof questionSurveyObj === 'undefined') { @@ -440,7 +447,7 @@ export function getRowData( function isRowFromCurrentGroupLevel( rowName: string, /** Null for root level rows. */ - groupPath: string|null, + groupPath: string | null, survey: SurveyRow[] ) { const flatPaths = getSurveyFlatPaths(survey, true); @@ -463,7 +470,10 @@ export function getRepeatGroupAnswers( // Goes through nested groups from key, looking for answers. const lookForAnswers = (data: SubmissionResponse, levelIndex: number) => { - const levelKey = targetKey.split('/').slice(0, levelIndex + 1).join('/'); + const levelKey = targetKey + .split('/') + .slice(0, levelIndex + 1) + .join('/'); // Each level could be an array of repeat group answers or object with questions. if (levelKey === targetKey) { if (Object.prototype.hasOwnProperty.call(data, targetKey)) { @@ -526,27 +536,25 @@ export function getMediaAttachment( fileName: string ): string | SubmissionAttachment { const validFileName = getValidFilename(fileName); - let mediaAttachment: string | SubmissionAttachment = t('Could not find ##fileName##').replace( - '##fileName##', - fileName, - ); + let mediaAttachment: string | SubmissionAttachment = t( + 'Could not find ##fileName##' + ).replace('##fileName##', fileName); submission._attachments.forEach((attachment) => { - if (attachment.filename.includes(validFileName)) { // Check if the audio filetype is of type not supported by player and send it to format to mp3 - if(attachment.mimetype.includes('audio/') - && !attachment.mimetype.includes('/mp3') - && !attachment.mimetype.includes('mpeg') - && !attachment.mimetype.includes('/wav') - && !attachment.mimetype.includes('ogg') - ) - { - const lastItem = attachment.filename.split("/").pop(); - const questionPath = Object.keys(submission).find(key => submission[key] === lastItem); - - const newAudioURL = - `${ROOT_URL}/api/v2/assets/${submission._xform_id_string}/data/${attachment.instance}/attachments/?xpath=${questionPath}&format=mp3`; + if ( + attachment.mimetype.includes('audio/') && + !attachment.mimetype.includes('/mp3') && + !attachment.mimetype.includes('mpeg') && + !attachment.mimetype.includes('/wav') && + !attachment.mimetype.includes('ogg') + ) { + const questionPath = Object.keys(submission).find( + (key) => submission[key] === fileName + ); + + const newAudioURL = `${ROOT_URL}/api/v2/assets/${submission._xform_id_string}/data/${attachment.instance}/attachments/?xpath=${questionPath}&format=mp3`; const newAttachment = { ...attachment, download_url: newAudioURL, @@ -554,7 +562,7 @@ export function getMediaAttachment( download_medium_url: newAudioURL, download_small_url: newAudioURL, mimetype: 'audio/mp3', - } + }; mediaAttachment = newAttachment; } else { mediaAttachment = attachment; @@ -623,43 +631,46 @@ export function getSupplementalDetailsContent( export function getRowSupplementalResponses( asset: AssetResponse, submissionData: SubmissionResponse, - rowName: string, + rowName: string ): DisplayResponse[] { const output: DisplayResponse[] = []; if (isRowProcessingEnabled(asset.uid, rowName)) { const advancedFeatures = asset.advanced_features; if (advancedFeatures?.transcript?.languages !== undefined) { - advancedFeatures.transcript.languages.forEach((languageCode: LanguageCode) => { - const path = getSupplementalTranscriptPath(rowName, languageCode); - output.push( - new DisplayResponse( - null, - getColumnLabel(asset, path, false), - path, - undefined, - getSupplementalDetailsContent(submissionData, path) - ) - ); - }); + advancedFeatures.transcript.languages.forEach( + (languageCode: LanguageCode) => { + const path = getSupplementalTranscriptPath(rowName, languageCode); + output.push( + new DisplayResponse( + null, + getColumnLabel(asset, path, false), + path, + undefined, + getSupplementalDetailsContent(submissionData, path) + ) + ); + } + ); } if (advancedFeatures?.translation?.languages !== undefined) { - advancedFeatures.translation.languages.forEach((languageCode: LanguageCode) => { - const path = getSupplementalTranslationPath(rowName, languageCode); - output.push( - new DisplayResponse( - null, - getColumnLabel(asset, path, false), - path, - undefined, - getSupplementalDetailsContent(submissionData, path) - ) - ); - }); + advancedFeatures.translation.languages.forEach( + (languageCode: LanguageCode) => { + const path = getSupplementalTranslationPath(rowName, languageCode); + output.push( + new DisplayResponse( + null, + getColumnLabel(asset, path, false), + path, + undefined, + getSupplementalDetailsContent(submissionData, path) + ) + ); + } + ); } } - return output; } @@ -668,10 +679,10 @@ export function getRowSupplementalResponses( * attachment is saved in storage. * See https://github.com/django/django/blob/832adb31f27cfc18ad7542c7eda5a1b6ed5f1669/django/utils/text.py#L224 */ -export function getValidFilename( - fileName: string -): string { - return fileName.normalize('NFD').replace(/\p{Diacritic}/gu, '') +export function getValidFilename(fileName: string): string { + return fileName + .normalize('NFD') + .replace(/\p{Diacritic}/gu, '') .replace(/ /g, '_') .replace(/[^\p{L}\p{M}\.\d_-]/gu, ''); } From 77ec4666c054e1065f6ba83bbb062ee1de84216f Mon Sep 17 00:00:00 2001 From: Leszek Date: Thu, 19 Oct 2023 15:33:00 +0200 Subject: [PATCH 3/3] add error handling for play action to MiniAudioPlayer --- jsapp/js/components/common/miniAudioPlayer.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jsapp/js/components/common/miniAudioPlayer.tsx b/jsapp/js/components/common/miniAudioPlayer.tsx index 92f27dcae4..8cb63c59c3 100644 --- a/jsapp/js/components/common/miniAudioPlayer.tsx +++ b/jsapp/js/components/common/miniAudioPlayer.tsx @@ -2,7 +2,7 @@ import React, {createRef} from 'react'; import bem, {makeBem} from 'js/bem'; import Icon from 'js/components/common/icon'; import Button from 'js/components/common/button'; -import {formatSeconds, generateUid} from 'js/utils'; +import {formatSeconds, generateUid, notify} from 'js/utils'; import 'js/components/common/miniAudioPlayer.scss'; bem.MiniAudioPlayer = makeBem(null, 'mini-audio-player'); @@ -132,9 +132,13 @@ class MiniAudioPlayer extends React.Component< } start() { - this.audioRef.current!.play(); - const event = new CustomEvent(PLAYER_STARTED_EVENT, {detail: this.uid}); - document.dispatchEvent(event); + const playPromise = this.audioRef.current!.play(); + playPromise.then(() => { + const event = new CustomEvent(PLAYER_STARTED_EVENT, {detail: this.uid}); + document.dispatchEvent(event); + }).catch((reason) => { + notify.error(reason.name + ' ' + reason.message); + }); } stop() {