-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tweak(adminPanel): RN-1419: Don't delete survey screens and components on import unless they have changed #5885
Merged
Merged
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9d1c543
Working check
alexd-bes 97c4dfb
Handle object equality with stringify
alexd-bes 7f527e9
Merge branch 'dev' into rn-1419-survey-importing
alexd-bes fcd350e
Fix object equality check
alexd-bes e56d960
Remove console log
alexd-bes 13b0e6f
Merge branch 'dev' into rn-1419-survey-importing
alexd-bes b6ee6f2
Merge pull request #6003 from beyondessential/dev
avaek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,10 +23,16 @@ import { | |
SURVEY_METADATA, | ||
} from './processSurveyMetadata'; | ||
import { caseAndSpaceInsensitiveEquals, convertCellToJson } from './utilities'; | ||
import { RECORDS } from '@tupaia/database'; | ||
|
||
const QUESTION_TYPE_LIST = Object.values(ANSWER_TYPES); | ||
const VIS_CRITERIA_CONJUNCTION = '_conjunction'; | ||
|
||
const objectsAreEqual = (a, b) => { | ||
if (!!a === !!b) return true; | ||
return JSON.stringify(a) === JSON.stringify(b); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this function is always going to be correct. I would probably just outsource this to lodash isEqual here since we need to do a deep equality check?! Or if you update the function it would probably be good to add a unit test for it |
||
|
||
const validateQuestionExistence = rows => { | ||
const isQuestionRow = ({ type }) => QUESTION_TYPE_LIST.includes(type); | ||
if (!rows || !rows.some(isQuestionRow)) { | ||
|
@@ -35,6 +41,115 @@ const validateQuestionExistence = rows => { | |
return true; | ||
}; | ||
|
||
/** | ||
* | ||
* @param {object} models | ||
* @param {string} screenId | ||
* @param {string} questionId | ||
* @param {number} componentNumber | ||
* @param {object} questionObject | ||
* | ||
* @returns {Promise<void>} | ||
* | ||
* @description Checks if the screen component already exists, if it does, it updates the changed values, otherwise it creates a new one | ||
*/ | ||
const updateOrCreateSurveyScreenComponent = async ( | ||
models, | ||
screenId, | ||
questionId, | ||
componentNumber, | ||
questionObject, | ||
) => { | ||
const existingScreenComponent = await models.surveyScreenComponent.findOne({ | ||
screen_id: screenId, | ||
question_id: questionId, | ||
component_number: componentNumber, | ||
}); | ||
const { | ||
questionLabel = null, | ||
detailLabel = null, | ||
visibilityCriteria = null, | ||
validationCriteria = null, | ||
} = questionObject; | ||
|
||
const validationCriteriaObject = convertCellToJson( | ||
validationCriteria, | ||
processValidationCriteriaValue, | ||
); | ||
// Create a new survey screen component to display this question | ||
const visibilityCriteriaObject = convertCellToJson(visibilityCriteria, splitStringOnComma); | ||
|
||
const processedVisibilityCriteria = {}; | ||
|
||
await Promise.all( | ||
Object.entries(visibilityCriteriaObject).map(async ([questionCode, answers]) => { | ||
if (questionCode === VIS_CRITERIA_CONJUNCTION) { | ||
// This is the special _conjunction key, extract the 'and' or the 'or' from answers, | ||
// i.e. { conjunction: ['and'] } -> { conjunction: 'and' } | ||
const [conjunctionType] = answers; | ||
processedVisibilityCriteria[VIS_CRITERIA_CONJUNCTION] = conjunctionType; | ||
} else if (questionCode === 'hidden') { | ||
processedVisibilityCriteria.hidden = answers[0] === 'true'; | ||
} else { | ||
const { id: questionId } = await models.question.findOne({ | ||
code: questionCode, | ||
}); | ||
processedVisibilityCriteria[questionId] = answers; | ||
} | ||
}), | ||
); | ||
|
||
// If the screen component already exists, update only the changed values, otherwise create a new one | ||
if (existingScreenComponent) { | ||
const changes = {}; | ||
if ( | ||
!objectsAreEqual( | ||
existingScreenComponent.visibility_criteria | ||
? JSON.parse(existingScreenComponent.visibility_criteria) | ||
: {}, | ||
processedVisibilityCriteria, | ||
) | ||
) { | ||
changes.visibility_criteria = JSON.stringify(processedVisibilityCriteria); | ||
} | ||
|
||
if ( | ||
!objectsAreEqual( | ||
existingScreenComponent.validation_criteria | ||
? JSON.parse(existingScreenComponent.validation_criteria) | ||
: {}, | ||
validationCriteriaObject, | ||
) | ||
) { | ||
changes.validation_criteria = JSON.stringify(validationCriteriaObject); | ||
} | ||
|
||
if (questionLabel !== existingScreenComponent.question_label) { | ||
changes.question_label = questionLabel; | ||
} | ||
|
||
if (detailLabel !== existingScreenComponent.detail_label) { | ||
changes.detail_label = detailLabel; | ||
} | ||
|
||
if (Object.keys(changes).length > 0) { | ||
await models.surveyScreenComponent.update({ id: existingScreenComponent.id }, changes); | ||
} | ||
return existingScreenComponent; | ||
} | ||
const newSurveyScreenComponent = await models.surveyScreenComponent.create({ | ||
screen_id: screenId, | ||
question_id: questionId, | ||
component_number: componentNumber, | ||
visibility_criteria: JSON.stringify(processedVisibilityCriteria), | ||
validation_criteria: JSON.stringify(validationCriteriaObject), | ||
question_label: questionLabel, | ||
detail_label: detailLabel, | ||
}); | ||
|
||
return newSurveyScreenComponent; | ||
}; | ||
|
||
const updateOrCreateDataElementInGroup = async ( | ||
models, | ||
dataElementCode, | ||
|
@@ -100,11 +215,16 @@ export async function importSurveysQuestions({ models, file, survey, dataGroup, | |
await dataGroup.deleteSurveyDateElement(); | ||
await dataGroup.upsertSurveyDateElement(); | ||
|
||
// Delete all existing survey screens and components that were attached to this survey | ||
await deleteScreensForSurvey(models, survey.id); | ||
const rows = xlsx.utils.sheet_to_json(sheet); | ||
validateQuestionExistence(rows); | ||
|
||
const questions = await survey.questions(); | ||
|
||
// If the questions have changed order or had questions added/removed, delete all screens from the survey and re-create them | ||
if (rows.map(({ code }) => code).join(',') !== questions.map(({ code }) => code).join(',')) { | ||
await deleteScreensForSurvey(models, survey.id); | ||
} | ||
|
||
// Add all questions to the survey, creating screens, components and questions as required | ||
let currentScreen; | ||
let currentSurveyScreenComponent; | ||
|
@@ -172,15 +292,11 @@ export async function importSurveysQuestions({ models, file, survey, dataGroup, | |
type, | ||
name, | ||
text, | ||
questionLabel, | ||
detail, | ||
detailLabel, | ||
options, | ||
optionLabels, | ||
optionColors, | ||
newScreen, | ||
visibilityCriteria, | ||
validationCriteria, | ||
optionSet, | ||
hook, | ||
} = questionObject; | ||
|
@@ -222,52 +338,30 @@ export async function importSurveysQuestions({ models, file, survey, dataGroup, | |
if (!currentScreen || shouldStartNewScreen) { | ||
// Spreadsheet indicates this question starts a new screen | ||
// Create a new survey screen | ||
currentScreen = await models.surveyScreen.create({ | ||
const params = { | ||
survey_id: survey.id, | ||
screen_number: currentScreen ? currentScreen.screen_number + 1 : 1, // Next screen | ||
}); | ||
}; | ||
const existingScreen = await models.surveyScreen.findOne(params); | ||
if (existingScreen) { | ||
currentScreen = existingScreen; | ||
} else { | ||
currentScreen = await models.surveyScreen.create(params); | ||
} | ||
// Clear existing survey screen component | ||
currentSurveyScreenComponent = undefined; | ||
} | ||
|
||
// Create a new survey screen component to display this question | ||
const visibilityCriteriaObject = await convertCellToJson( | ||
visibilityCriteria, | ||
splitStringOnComma, | ||
); | ||
const processedVisibilityCriteria = {}; | ||
await Promise.all( | ||
Object.entries(visibilityCriteriaObject).map(async ([questionCode, answers]) => { | ||
if (questionCode === VIS_CRITERIA_CONJUNCTION) { | ||
// This is the special _conjunction key, extract the 'and' or the 'or' from answers, | ||
// i.e. { conjunction: ['and'] } -> { conjunction: 'and' } | ||
const [conjunctionType] = answers; | ||
processedVisibilityCriteria[VIS_CRITERIA_CONJUNCTION] = conjunctionType; | ||
} else if (questionCode === 'hidden') { | ||
processedVisibilityCriteria.hidden = answers[0] === 'true'; | ||
} else { | ||
const { id: questionId } = await models.question.findOne({ | ||
code: questionCode, | ||
}); | ||
processedVisibilityCriteria[questionId] = answers; | ||
} | ||
}), | ||
const componentNumber = currentSurveyScreenComponent | ||
? currentSurveyScreenComponent.component_number + 1 | ||
: 1; | ||
currentSurveyScreenComponent = await updateOrCreateSurveyScreenComponent( | ||
models, | ||
currentScreen.id, | ||
question.id, | ||
componentNumber, | ||
questionObject, | ||
); | ||
|
||
currentSurveyScreenComponent = await models.surveyScreenComponent.create({ | ||
screen_id: currentScreen.id, | ||
question_id: question.id, | ||
component_number: currentSurveyScreenComponent | ||
? currentSurveyScreenComponent.component_number + 1 | ||
: 1, | ||
visibility_criteria: JSON.stringify(processedVisibilityCriteria), | ||
validation_criteria: JSON.stringify( | ||
convertCellToJson(validationCriteria, processValidationCriteriaValue), | ||
), | ||
question_label: questionLabel, | ||
detail_label: detailLabel, | ||
}); | ||
|
||
const componentId = currentSurveyScreenComponent.id; | ||
await configImporter.add(rowIndex, componentId, constructImportValidationError); | ||
} catch (e) { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.