Skip to content

Commit

Permalink
Feat/add translate nomenclatures task (#1184)
Browse files Browse the repository at this point in the history
Autofill missing local language fields in forms
  • Loading branch information
onyxvd authored Sep 9, 2024
1 parent 0a951cd commit 7f666bb
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 3 deletions.
104 changes: 104 additions & 0 deletions __tests__/tasks/autoTranslateNomenclatures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* eslint-env node, jest */
/* globals setup */

const formBirdsFactory = require('../../__utils__/factories/formBirdsFactory')
const speciesFactory = require('../../__utils__/factories/speciesFactory')

const run = (...args) => setup.api.tasks.tasks.autoTranslateNomenclatures.run(...args)

beforeAll(async () => {
await setup.api.models.nomenclature.create({
type: 'birds_age',
labelEn: 'adult',
labelBg: 'възрастен'
})

await setup.api.models.nomenclature.create({
type: 'birds_behaviour',
labelEn: 'singing',
labelBg: 'пеене'
})

await setup.api.models.nomenclature.create({
type: 'birds_behaviour',
labelEn: 'feeding',
labelBg: 'хранене'
})
})

describe('autoTranslateNomenclatures task', () => {
it('populates *Local for single choice nomenclature fields when missing', async () => {
const species = await speciesFactory(setup.api)
const record = await formBirdsFactory(setup.api, {
species,
ageEn: 'adult'
})

await run({ form: 'formBirds', id: record.id })

const reloaded = await record.reload()
expect(reloaded.dataValues).toEqual(expect.objectContaining({
ageLocal: 'възрастен'
}))
})

it('populates *Local for multiple choice nomenclature fields when missing', async () => {
const species = await speciesFactory(setup.api)
const record = await formBirdsFactory(setup.api, {
species,
behaviourEn: 'singing | feeding'
})

await run({ form: 'formBirds', id: record.id })

const reloaded = await record.reload()
expect(reloaded.dataValues).toEqual(expect.objectContaining({
behaviourLocal: 'пеене | хранене'
}))
})

it('populates *Local for multiple choice nomenclature fields with single value', async () => {
const species = await speciesFactory(setup.api)
const record = await formBirdsFactory(setup.api, {
species,
behaviourEn: 'singing'
})

await run({ form: 'formBirds', id: record.id })

const reloaded = await record.reload()
expect(reloaded.dataValues).toEqual(expect.objectContaining({
behaviourLocal: 'пеене'
}))
})

it('populates *Lang form fields when add missing translations', async () => {
const species = await speciesFactory(setup.api)
const record = await formBirdsFactory(setup.api, {
species,
ageEn: 'adult'
})

await run({ form: 'formBirds', id: record.id })

const reloaded = await record.reload()
expect(reloaded.dataValues).toEqual(expect.objectContaining({
ageLang: 'bg'
}))
})

it('Set *Local to "|" when nomenclature is not found', async () => {
const species = await speciesFactory(setup.api)
const record = await formBirdsFactory(setup.api, {
species,
ageEn: 'missing-value'
})

await run({ form: 'formBirds', id: record.id })

const reloaded = await record.reload()
expect(reloaded.dataValues).toEqual(expect.objectContaining({
ageLocal: '|'
}))
})
})
12 changes: 12 additions & 0 deletions server/actions/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,15 @@ module.exports.birdsNewSpeciesModeratorReview = class EnqueueAutoVisit extends B
return await api.tasks.enqueue('birdsNewSpeciesModeratorReview', { form, id, limit, force })
}
}

module.exports.autoTranslateNomenclatures = class EnqueueAutoTranslateNomenclatures extends BaseAction {
constructor () {
super()
this.name = 'tasks:enqueue:autoTranslateNomenclatures'
this.description = 'Trigger autoTranslateNomenclatures'
}

async enqueue ({ form, id, limit, force }) {
return await api.tasks.enqueue('autoTranslateNomenclatures', { form, id, limit, force })
}
}
4 changes: 2 additions & 2 deletions server/classes/FormsTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = class FormsTask extends Task {
return Object.values(api.forms).filter((form) => form.$isForm)
}

filterRecords ({ force = false }) {
filterRecords ({ force = false, form = null }) {
throw new Error(`You need to implement filterRecords in ${this.name} to return a sequelize where filter`)
}

Expand Down Expand Up @@ -42,7 +42,7 @@ module.exports = class FormsTask extends Task {
: {
[Op.and]: [
lastId != null ? { id: { [Op.lt]: lastId } } : {},
this.filterRecords({ force }),
this.filterRecords({ force, form }),
filter
]
},
Expand Down
4 changes: 4 additions & 0 deletions server/config/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ exports.default = {
visit: {
// max records per task
maxRecords: parseInt(process.env.AUTO_VISIT_MAX_RECORDS, 10) || 100
},
translate: {
// max records per task
maxRecords: parseInt(process.env.AUTO_TRANSLATE_MAX_RECORDS, 10) || 100
}
}
}
Expand Down
1 change: 1 addition & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ exports.default = {
{ path: '/visit/:year', action: 'visit:edit' },
{ path: '/zone/:id/owner', action: 'zone:requestOwnership' },
{ path: '/zone/:id/owner/response', action: 'zone:respondOwnershipRequest' },
{ path: '/tasks/auto-translate-nomenclatures', action: 'tasks:enqueue:autoTranslateNomenclatures' },

// forms
{ path: '/bats', action: 'formBats:create' },
Expand Down
111 changes: 111 additions & 0 deletions server/tasks/autoTranslateNomenclatures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { api } = require('actionhero')
const sequelize = require('sequelize')
const FormsTask = require('../classes/FormsTask')

module.exports = class AutoTranslateNomenclatures extends FormsTask {
constructor () {
super()
this.name = 'autoTranslateNomenclatures'
this.description = 'Translate nomenclatures'
// use cronjob to schedule the task
// npm run enqueue autoTranslateNomenclatures
this.frequency = 0
this.defaultLimit = api.config.app.translate.maxRecords
}

findNomenclatureFields = (form) => {
return Object.entries(api.forms[form].fields).map(([key, field]) => {
if (field.relation?.model === 'nomenclature') {
return {
key,
type: field.type,
nomenclature: field.relation?.filter?.type
}
}
return null
}).filter(Boolean)
}

filterRecords ({ force, form }) {
// fore all
if (force === true) return {}

const nomenclatureFields = this.findNomenclatureFields(form)

// no nomenclature fields
if (nomenclatureFields.length === 0) {
return {}
}

return {
[sequelize.Op.or]: nomenclatureFields.map(({ key, type, nomenclature }) => {
return {
[sequelize.Op.and]: {
[`${key}En`]: {
[sequelize.Op.and]: {
[sequelize.Op.not]: null,
[sequelize.Op.ne]: ''
}
},
[`${key}Local`]: {
[sequelize.Op.or]: {
[sequelize.Op.is]: null,
[sequelize.Op.eq]: ''
}
}
}
}
})
}
}

async processRecord (record, form) {
const nomenclatureFields = this.findNomenclatureFields(form)

for (const nomenclatureField of nomenclatureFields) {
const key = nomenclatureField.key

if (record[`${key}En`] !== null && record[`${key}En`] !== '' && (record[`${key}Local`] === null || record[`${key}Local`] === '')) {
if (nomenclatureField.type === 'choice') {
const nomenclatures = await api.models.nomenclature.findAll({
attributes: ['labelBg'],
where: {
type: nomenclatureField.nomenclature,
labelEn: record[key + 'En']
}
})
if (nomenclatures?.length > 0 && nomenclatures[0].labelBg) {
record[key + 'Local'] = nomenclatures[0].labelBg
record[key + 'Lang'] = 'bg'
} else {
record[key + 'Local'] = '|'
record[key + 'Lang'] = null
}
} else if (nomenclatureField.type === 'multi') {
const enValues = record[key + 'En'].split(' | ')

const nomenclatures = await api.models.nomenclature.findAll({
attributes: ['labelBg', 'labelEn'],
where: {
type: nomenclatureField.nomenclature,
labelEn: enValues
}
})

if (nomenclatures?.length > 0 && nomenclatures.length === enValues.length) {
record[key + 'Local'] = enValues.map((enValue) => {
const nomenclature = nomenclatures.find((nomenclature) => nomenclature.labelEn === enValue)
return nomenclature?.labelBg
}).filter(Boolean).join(' | ')
record[key + 'Lang'] = 'bg'
} else {
record[key + 'Local'] = '|'
record[key + 'Lang'] = null
}
}
}
}

await api.forms.trySave(record, api.forms[form])
}
}
2 changes: 1 addition & 1 deletion server/utils/localField.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function localField (prefix, {
}

// fill the local label if available
if (model[langFieldName] != null && model[localFieldName]) {
if (model[langFieldName] != null && model[localFieldName] && model[localFieldName] !== '|') {
vals[genKey(getLocalLang(model, prefix, langFieldName))] = model[localFieldName]
}

Expand Down

0 comments on commit 7f666bb

Please sign in to comment.