Skip to content

Commit

Permalink
assign unique survey name when importing survey from Collect (#1622)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefano Ricci <SteRiccio@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 26, 2021
1 parent a62dcda commit 86d113e
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 36 deletions.
17 changes: 12 additions & 5 deletions core/survey/_surveyValidator/surveyInfoValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import * as Validation from '@core/validation/validation'

import * as SurveyCyclesValidator from './surveyCyclesValidator'

const validateSurveyNameUniqueness = surveyInfos => (propName, survey) => {
return !R.isEmpty(surveyInfos) && R.find(s => s.id !== survey.id, surveyInfos)
const validateSurveyNameUniqueness = (surveyInfos) => (propName, survey) =>
!R.isEmpty(surveyInfos) && R.find((s) => s.id !== survey.id, surveyInfos)
? { key: Validation.messageKeys.nameDuplicate }
: null
}

export const validateNewSurvey = async (survey, surveyInfos) =>
await Validator.validate(survey, {
export const validateNewSurvey = async ({ newSurvey, surveyInfos }) =>
Validator.validate(newSurvey, {
name: [
Validator.validateRequired(Validation.messageKeys.nameRequired),
Validator.validateNotKeyword(Validation.messageKeys.nameCannotBeKeyword),
Expand All @@ -22,6 +21,14 @@ export const validateNewSurvey = async (survey, surveyInfos) =>
lang: [Validator.validateRequired(Validation.messageKeys.surveyInfoEdit.langRequired)],
})

export const validateSurveyClone = async ({ newSurvey, surveyInfos }) =>
Validator.validate(newSurvey, {
name: [
Validator.validateNotKeyword(Validation.messageKeys.nameCannotBeKeyword),
validateSurveyNameUniqueness(surveyInfos),
],
})

export const validateSurveyInfo = async (surveyInfo, surveyInfos) => {
const validation = await Validator.validate(surveyInfo, {
'props.name': [
Expand Down
4 changes: 1 addition & 3 deletions core/survey/surveyValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import * as SurveyInfoValidator from './_surveyValidator/surveyInfoValidator'
import * as NodeDefValidator from './_surveyValidator/nodeDefValidator'
import * as NodeDefExpressionsValidator from './_surveyValidator/nodeDefExpressionsValidator'

export const { validateNewSurvey } = SurveyInfoValidator

export const { validateSurveyInfo } = SurveyInfoValidator
export const { validateNewSurvey, validateSurveyClone, validateSurveyInfo } = SurveyInfoValidator

export const { validateNodeDef } = NodeDefValidator

Expand Down
26 changes: 16 additions & 10 deletions server/modules/collectImport/api/collectImportApi.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import * as Request from '@server/utils/request'

import * as JobUtils from '@server/job/jobUtils'
import * as DateUtils from '@core/dateUtils'
import * as Survey from '@core/survey/survey'
import * as Validation from '@core/validation/validation'

import { db } from '@server/db/db'
import * as JobUtils from '@server/job/jobUtils'
import * as Request from '@server/utils/request'
import * as Response from '@server/utils/response'
import * as CSVWriter from '@server/utils/file/csvWriter'

import * as SurveyService from '@server/modules/survey/service/surveyService'
import * as Response from '@server/utils/response'

import * as DateUtils from '@core/dateUtils'
import * as Survey from '@core/survey/survey'
import * as CollectImportService from '../service/collectImportService'
import * as AuthMiddleware from '../../auth/authApiMiddleware'

Expand All @@ -19,10 +20,15 @@ export const init = (app) => {
try {
const user = Request.getUser(req)
const file = Request.getFile(req)

const job = CollectImportService.startCollectImportJob(user, file.tempFilePath)

res.json({ job: JobUtils.jobToJSON(job) })
const newSurvey = Request.getJsonParam(req, 'survey')
const validation = await SurveyService.validateSurveyClone({ newSurvey })

if (Validation.isValid(validation)) {
const job = CollectImportService.startCollectImportJob({ user, filePath: file.tempFilePath, newSurvey })
res.json({ job: JobUtils.jobToJSON(job) })
} else {
res.json({ validation })
}
} catch (error) {
next(error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import * as ActivityLogManager from '../../../../activityLog/manager/activityLog
import * as SurveyManager from '../../../../survey/manager/surveyManager'

import * as CollectSurvey from '../model/collectSurvey'
import { findUniqueSurveyName } from './surveyUniqueNameGenerator'

export default class SurveyCreatorJob extends Job {
constructor(params) {
super('SurveyCreatorJob', params)
}

async execute() {
const { collectSurvey } = this.context
const { collectSurvey, newSurvey: newSurveyParam } = this.context

const collectUri = CollectSurvey.getChildElementText('uri')(collectSurvey)

const name = R.pipe(R.split('/'), R.last)(collectUri)
const startingName = newSurveyParam.name || R.pipe(R.split('/'), R.last)(collectUri)
const name = await findUniqueSurveyName({ startingName })

const languages = R.pipe(CollectSurvey.getElementsByName('language'), R.map(CollectSurvey.getText))(collectSurvey)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { db } from '@server/db/db'
import * as SurveyManager from '@server/modules/survey/manager/surveyManager'

const nameWithCountPattern = /^(.*)_(\d{1,2})$/ // everything ending with _ followed by 1 or 2 decimals

const parseName = (name) => {
const match = name.match(nameWithCountPattern)
return match
? {
nameWithoutCount: match[1],
count: Number(match[2]),
}
: {
nameWithoutCount: name,
count: 0,
}
}

export const findUniqueSurveyName = async ({ startingName, client = db }) => {
const surveyIdsAndNames = await SurveyManager.fetchSurveyIdsAndNames(client)

const isDuplicate = (name) => surveyIdsAndNames.some(({ name: surveyName }) => surveyName === name)

// extract count from startingName (suffix _NN added to the survey name, if any)
let currentName = startingName
let { count, nameWithoutCount } = parseName(startingName)

while (isDuplicate(currentName)) {
count += 1
currentName = `${nameWithoutCount}_${count}`
}
return currentName
}
4 changes: 2 additions & 2 deletions server/modules/collectImport/service/collectImportService.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import * as CollectImportReportManager from '../manager/collectImportReportManag
import CollectImportJob from './collectImport/collectImportJob'

// COLLECT SURVEY IMPORT
export const startCollectImportJob = (user, filePath) => {
const job = new CollectImportJob({ user, filePath })
export const startCollectImportJob = ({ user, filePath, newSurvey }) => {
const job = new CollectImportJob({ user, filePath, newSurvey })

JobManager.executeJobThread(job)

Expand Down
2 changes: 1 addition & 1 deletion server/modules/survey/api/surveyApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const init = (app) => {
try {
const user = Request.getUser(req)
const surveyReq = Request.getBody(req)
const validation = await SurveyService.validateNewSurvey(surveyReq)
const validation = await SurveyService.validateNewSurvey({ newSurvey: surveyReq })

if (Validation.isValid(validation)) {
const { name, label, lang, cloneFrom = null, template = false } = surveyReq
Expand Down
18 changes: 15 additions & 3 deletions server/modules/survey/manager/surveyManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ const assocSurveyInfo = (info) => ({ info })

// ====== VALIDATION

export const validateNewSurvey = async (newSurvey) => {
export const validateNewSurvey = async ({ newSurvey }) => {
const surveyInfos = await SurveyRepository.fetchSurveysByName(newSurvey.name) // TODO add object model for newSurvey
return SurveyValidator.validateNewSurvey(newSurvey, surveyInfos)
return SurveyValidator.validateNewSurvey({ newSurvey, surveyInfos })
}

export const validateSurveyClone = async ({ newSurvey }) => {
const surveyInfos = await SurveyRepository.fetchSurveysByName(newSurvey.name)
return SurveyValidator.validateSurveyClone({ newSurvey, surveyInfos })
}

const validateSurveyInfo = async (surveyInfo) =>
Expand Down Expand Up @@ -155,7 +160,14 @@ export const importSurvey = async (params, client = db) => {
}

// ====== READ
export const { countOwnedSurveys, countUserSurveys, fetchAllSurveyIds, fetchDependencies } = SurveyRepository
export const {
countOwnedSurveys,
countUserSurveys,
fetchAllSurveyIds,
fetchSurveysByName,
fetchSurveyIdsAndNames,
fetchDependencies,
} = SurveyRepository

export const fetchSurveyById = async ({ surveyId, draft = false, validate = false, backup = false }, client = db) => {
const [surveyInfo, authGroups] = await Promise.all([
Expand Down
9 changes: 9 additions & 0 deletions server/modules/survey/repository/surveyRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ export const fetchSurveysByName = async (surveyName, client = db) =>
(def) => DB.transformCallback(def)
)

export const fetchSurveyIdsAndNames = async (client = db) =>
client.any(
`SELECT id, props->>'name' as name
FROM survey WHERE props->>'name' IS NOT NULL
UNION
SELECT id, props_draft->>'name' as name
FROM survey WHERE props_draft->>'name' IS NOT NULL`
)

export const fetchSurveyById = async ({ surveyId, draft = false, backup = false }, client = db) =>
client.one(`SELECT ${surveySelectFields()} FROM survey WHERE id = $1`, [surveyId], (def) =>
DB.transformCallback(def, draft, false, backup)
Expand Down
1 change: 1 addition & 0 deletions server/modules/survey/service/surveyService.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export const {
deleteTemporarySurveys,
// UTILS
validateNewSurvey,
validateSurveyClone,
} = SurveyManager
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const useActions = ({ newSurvey, setNewSurvey }) => ({
onUpdate: useOnUpdate({ newSurvey, setNewSurvey }),
onCreate: useOnCreate({ newSurvey, setNewSurvey }),
onImport: {
Collect: useOnImport({ source: importSources.collect }),
Collect: useOnImport({ newSurvey, setNewSurvey, source: importSources.collect }),
Arena: useOnImport({ source: importSources.arena }),
},
})
29 changes: 20 additions & 9 deletions webapp/components/survey/SurveyCreate/store/actions/useOnImport.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import axios from 'axios'
import { useDispatch } from 'react-redux'

import * as Validation from '@core/validation/validation'

import { SurveyActions } from '@webapp/store/survey'
import { JobActions } from '@webapp/store/app'
import * as JobSerialized from '@common/job/jobSerialized'
Expand All @@ -13,25 +15,34 @@ const urlBasedOnSource = {
[importSources.collect]: '/api/survey/collect-import',
[importSources.arena]: '/api/survey/arena-import',
}
export const useOnImport = ({ source = importSources.collect }) => {
export const useOnImport = ({ newSurvey, setNewSurvey, source = importSources.collect }) => {
const dispatch = useDispatch()

return ({ file }) => {
;(async () => {
const formData = new FormData()
formData.append('file', file)
formData.append('survey', JSON.stringify(newSurvey))

const { data } = await axios.post(urlBasedOnSource[source], formData)
const { job, validation } = data

dispatch(
JobActions.showJobMonitor({
job: data.job,
onComplete: async (job) => {
const { surveyId } = JobSerialized.getResult(job)
dispatch(SurveyActions.setActiveSurvey(surveyId, true, true))
},
if (job && (!validation || Validation.isValid(validation))) {
dispatch(
JobActions.showJobMonitor({
job: data.job,
onComplete: async (job) => {
const { surveyId } = JobSerialized.getResult(job)
dispatch(SurveyActions.setActiveSurvey(surveyId, true, true))
},
})
)
} else if (validation && !Validation.isValid(validation)) {
setNewSurvey({
...newSurvey,
validation,
})
)
}
})()
}
}

0 comments on commit 86d113e

Please sign in to comment.