From fa926f340e9a244aff539651d8b70f92b2f49212 Mon Sep 17 00:00:00 2001 From: Stefano Ricci <1219739+SteRiccio@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:59:13 +0200 Subject: [PATCH] Chain: ignore cycles (#3575) * Make chain available to all cycles (WIP) * chain: ignore cycles --------- Co-authored-by: Stefano Ricci Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- common/analysis/chain.js | 2 - common/model/db/tables/chain/select.js | 6 +-- server/modules/analysis/api/chainApi.js | 12 +++--- .../modules/analysis/manager/chain/index.js | 40 ++++++++----------- server/modules/analysis/manager/index.js | 12 +----- .../analysis/repository/chain/delete.js | 17 +++----- .../analysis/repository/chain/index.js | 2 +- .../modules/analysis/repository/chain/read.js | 14 +++---- .../analysis/repository/chain/update.js | 23 +---------- .../analysis/service/chainSummaryGenerator.js | 1 - .../publish/jobs/chainsCyclesCheckJob.js | 16 +------- webapp/components/form/Input/Input.js | 18 ++++----- webapp/components/form/SimpleTextInput.js | 4 +- .../views/Analysis/Chain/ChainBasicProps.js | 5 --- .../views/App/views/Analysis/Chains/Chains.js | 4 +- 15 files changed, 51 insertions(+), 125 deletions(-) diff --git a/common/analysis/chain.js b/common/analysis/chain.js index b7b69b760a..75e5d236d5 100644 --- a/common/analysis/chain.js +++ b/common/analysis/chain.js @@ -24,7 +24,6 @@ export const keys = { export const keysProps = { labels: ObjectUtils.keysProps.labels, descriptions: ObjectUtils.keysProps.descriptions, - cycles: ObjectUtils.keysProps.cycles, hasSamplingDesign: 'hasSamplingDesign', samplingDesign: 'samplingDesign', analysisNodeDefs: 'analysisNodeDefs', @@ -46,7 +45,6 @@ export const { getUuid, getProps, getPropsDiff, - getCycles, getDateCreated, getDateModified, getDescriptions, diff --git a/common/model/db/tables/chain/select.js b/common/model/db/tables/chain/select.js index 1662faad32..0aa4d400c6 100644 --- a/common/model/db/tables/chain/select.js +++ b/common/model/db/tables/chain/select.js @@ -1,5 +1,3 @@ -import * as Chain from '@common/analysis/chain' - function _getSelectFields({ count, includeScript }) { if (count) { return ['count(*)'] @@ -14,7 +12,6 @@ function _getSelectFields({ count, includeScript }) { * Generate the select query for the processing_chain table by the given parameters. * * @param {!object} params - The query parameters. - * @param {string} [params.cycle=null] - The survey cycle to filter by. * @param {string} [params.chainUuid=null] - The chain uuid to filter by. * @param {boolean} [params.count=false] - Whether to count. * @param {boolean} [params.includeScript=false] - Whether to include R scripts. @@ -22,7 +19,7 @@ function _getSelectFields({ count, includeScript }) { * @returns {string} - The select query. */ export function getSelect(params) { - const { cycle = null, chainUuid = null, count = false, includeScript = false } = params + const { chainUuid = null, count = false, includeScript = false } = params this.getSelectFields = _getSelectFields.bind(this) @@ -31,7 +28,6 @@ export function getSelect(params) { FROM ${this.nameAliased} - ${cycle ? `WHERE (${this.columnProps})->'${Chain.keysProps.cycles}' @> '"${cycle}"'` : ''} ${chainUuid ? `WHERE ${this.columnUuid} = '${chainUuid}'` : ''} ` } diff --git a/server/modules/analysis/api/chainApi.js b/server/modules/analysis/api/chainApi.js index 6e8f13d175..ee3d41ad8b 100644 --- a/server/modules/analysis/api/chainApi.js +++ b/server/modules/analysis/api/chainApi.js @@ -14,10 +14,10 @@ export const init = (app) => { app.post('/survey/:surveyId/chain/', AuthMiddleware.requireRecordAnalysisPermission, async (req, res, next) => { try { - const { cycle, surveyId } = Request.getParams(req) + const { surveyId } = Request.getParams(req) const user = Request.getUser(req) - const chain = await AnalysisService.create({ user, surveyId, cycle }) + const chain = await AnalysisService.create({ user, surveyId }) res.json(chain) } catch (error) { @@ -32,9 +32,9 @@ export const init = (app) => { AuthMiddleware.requireRecordAnalysisPermission, async (req, res, next) => { try { - const { surveyId, surveyCycleKey: cycle, offset, limit } = Request.getParams(req) + const { surveyId, offset, limit } = Request.getParams(req) - const list = await AnalysisService.fetchChains({ surveyId, cycle, offset, limit }) + const list = await AnalysisService.fetchChains({ surveyId, offset, limit }) res.json({ list }) } catch (error) { @@ -48,9 +48,9 @@ export const init = (app) => { AuthMiddleware.requireRecordAnalysisPermission, async (req, res, next) => { try { - const { surveyId, surveyCycleKey: cycle } = Request.getParams(req) + const { surveyId } = Request.getParams(req) - const count = await AnalysisService.countChains({ surveyId, cycle }) + const count = await AnalysisService.countChains({ surveyId }) res.json({ count }) } catch (error) { diff --git a/server/modules/analysis/manager/chain/index.js b/server/modules/analysis/manager/chain/index.js index 21050ef8cd..0badd579b2 100644 --- a/server/modules/analysis/manager/chain/index.js +++ b/server/modules/analysis/manager/chain/index.js @@ -28,27 +28,26 @@ export const insertChain = async ({ user, surveyId, chain }, client = DB.client) return chainDb }) -export const create = async ({ user, surveyId, cycle }) => { - let newChain = ChainFactory.createInstance({ cycles: [cycle] }) - +export const create = async ({ user, surveyId }) => { const survey = await SurveyManager.fetchSurveyAndNodeDefsBySurveyId({ surveyId, draft: true, advanced: true }) - const defaultLang = Survey.getDefaultLanguage(Survey.getSurveyInfo(survey)) - const validation = await ChainValidator.validateChain({ chain: newChain, defaultLang, survey }) + const surveyInfo = Survey.getSurveyInfo(survey) + const cycles = Survey.getCycleKeys(surveyInfo) + const defaultLang = Survey.getDefaultLanguage(surveyInfo) - newChain = Chain.assocValidation(validation)(newChain) + let chain = ChainFactory.createInstance({ cycles }) - return insertChain({ - surveyId, - user, - chain: newChain, - }) + const validation = await ChainValidator.validateChain({ chain, defaultLang, survey }) + + chain = Chain.assocValidation(validation)(chain) + + return insertChain({ surveyId, user, chain }) } // ====== READ export const { countChains, fetchChains, fetchChain } = ChainRepository // ====== UPDATE -export const { updateChain, removeChainCycles } = ChainRepository +export const { updateChain } = ChainRepository export const updateChainStatusExec = async ({ user, surveyId, chainUuid, statusExec }) => DB.client.tx(async (tx) => { @@ -101,28 +100,26 @@ export const persistChain = async ({ user, surveyId, chain }, client) => { // ====== DELETE -export const _deleteChain = async ({ user, surveyId, chainUuid = false, noCycle = false }, client = DB.client) => { - const deletedChains = await ChainRepository.deleteChain({ surveyId, chainUuid, noCycle }, client) - - const deletedChainsUuids = deletedChains.map(Chain.getUuid) +export const _deleteChain = async ({ user, surveyId, chainUuid }, client = DB.client) => { + const deletedChain = await ChainRepository.deleteChain({ surveyId, chainUuid }, client) + const deletedChainUuid = Chain.getUuid(deletedChain) const survey = await SurveyManager.fetchSurveyAndNodeDefsBySurveyId( { surveyId, draft: true, advanced: true, includeAnalysis: true }, (client = DB.client) ) const nodeDefsUuidsInDeleteChains = Survey.getNodeDefsArray(survey) - .filter((_nodeDef) => deletedChainsUuids.includes(NodeDef.getChainUuid(_nodeDef))) + .filter((_nodeDef) => deletedChainUuid === NodeDef.getChainUuid(_nodeDef)) .map(NodeDef.getUuid) await NodeDefService.markNodeDefsDeleted({ user, surveyId, nodeDefUuids: nodeDefsUuidsInDeleteChains }, client) - return deletedChains + return deletedChain } export const deleteChain = async ({ user, surveyId, chainUuid }, client = DB.client) => client.tx(async (tx) => { - const deletedChains = await _deleteChain({ user, surveyId, chainUuid }, tx) - const deletedChain = deletedChains[0] + const deletedChain = await _deleteChain({ user, surveyId, chainUuid }, tx) const content = { [ActivityLog.keysContent.uuid]: chainUuid, @@ -133,6 +130,3 @@ export const deleteChain = async ({ user, surveyId, chainUuid }, client = DB.cli markSurveyDraft(surveyId, tx), ]) }) - -export const deleteChainWithoutCycle = async ({ surveyId }, client = DB.client) => - client.tx(async (tx) => _deleteChain({ surveyId, noCycle: true }, tx)) diff --git a/server/modules/analysis/manager/index.js b/server/modules/analysis/manager/index.js index e68457cd53..8eb646dd3f 100644 --- a/server/modules/analysis/manager/index.js +++ b/server/modules/analysis/manager/index.js @@ -1,14 +1,4 @@ // ====== Chain -export { - create, - countChains, - fetchChains, - fetchChain, - updateChain, - updateChainStatusExec, - removeChainCycles, - deleteChain, - deleteChainWithoutCycle, -} from './chain' +export { create, countChains, fetchChains, fetchChain, updateChain, updateChainStatusExec, deleteChain } from './chain' export { cleanChains } from './chainsCleanManager' diff --git a/server/modules/analysis/repository/chain/delete.js b/server/modules/analysis/repository/chain/delete.js index 14adfe755d..589dc51784 100644 --- a/server/modules/analysis/repository/chain/delete.js +++ b/server/modules/analysis/repository/chain/delete.js @@ -1,5 +1,3 @@ -import * as Chain from '@common/analysis/chain' - import * as DB from '../../../../db' import { TableChain } from '../../../../../common/model/db' @@ -9,26 +7,21 @@ import { TableChain } from '../../../../../common/model/db' * * @param {!object} params - The query parameters. * @param {!string} params.surveyId - The survey id. - * @param {string} [params.chainUuid=null] - The chain uuid to delete. - * @param {boolean} [params.noCycle=false] - Whether to delete chains with no cycles. + * @param {!string} [params.chainUuid] - The chain uuid to delete. * @param {pgPromise.IDatabase} [client=db] - The database client. * * @returns {Promise} - The result promise. */ export const deleteChain = async (params, client = DB.client) => { - const { surveyId, chainUuid = null, noCycle = false } = params - - if (!chainUuid && !noCycle) - throw new Error(`One between chainUuid and noCycle are required. {chainUuid:${chainUuid}, noCycle:${noCycle}}`) + const { surveyId, chainUuid } = params const tableChain = new TableChain(surveyId) - return client.any( + return client.one( `DELETE FROM ${tableChain.nameQualified} - WHERE - ${chainUuid ? TableChain.columnSet.uuid : `jsonb_array_length(props->'${Chain.keysProps.cycles}')`} = $1 + WHERE ${TableChain.columnSet.uuid} = $1 RETURNING *`, - chainUuid ? [chainUuid] : [0], + [chainUuid], DB.transformCallback ) } diff --git a/server/modules/analysis/repository/chain/index.js b/server/modules/analysis/repository/chain/index.js index ec11ead77a..6aeacecdc5 100644 --- a/server/modules/analysis/repository/chain/index.js +++ b/server/modules/analysis/repository/chain/index.js @@ -3,6 +3,6 @@ export { insertMany } from './insert' export { countChains, fetchChains, fetchChain } from './read' -export { updateChain, removeChainCycles } from './update' +export { updateChain } from './update' export { deleteChain } from './delete' diff --git a/server/modules/analysis/repository/chain/read.js b/server/modules/analysis/repository/chain/read.js index a161760bbf..21e16bd3ea 100644 --- a/server/modules/analysis/repository/chain/read.js +++ b/server/modules/analysis/repository/chain/read.js @@ -13,27 +13,25 @@ export const transformCallback = (row) => { } /** - * Count the processing chains by the given survey id and the optional survey cycle. + * Count the processing chains by the given survey id. * * @param {!object} params - The query parameters. * @param {!string} params.surveyId - The survey id. - * @param {string} [params.cycle=null] - The survey cycle. * @param {BaseProtocol} [client=db] - The database client. * * @returns {Promise} - The result promise. */ export const countChains = async (params, client = DB.client) => { - const { surveyId, cycle = null } = params + const { surveyId } = params const tableChain = new TableChain(surveyId) - return client.one(`${tableChain.getSelect({ surveyId, cycle, count: true })}`, [], (row) => Number(row.count)) + return client.one(`${tableChain.getSelect({ surveyId, count: true })}`, [], (row) => Number(row.count)) } /** - * Fetches all processing chains by the given survey id and the optional survey cycle. + * Fetches all processing chains by the given survey id. * * @param {!object} params - The query parameters. * @param {!string} params.surveyId - The survey id. - * @param {string} [params.cycle=null] - The survey cycle. * @param {number} [params.offset=0] - The select query offset. * @param {number} [params.limit=null] - The select query limit. * @param {boolean} [params.includeScript=false] - Whether to include the R scripts. @@ -42,12 +40,12 @@ export const countChains = async (params, client = DB.client) => { * @returns {Promise} - The result promise. */ export const fetchChains = async (params, client = DB.client) => { - const { surveyId, cycle = null, offset = 0, limit = null, includeScript = false } = params + const { surveyId, offset = 0, limit = null, includeScript = false } = params const tableChain = new TableChain(surveyId) return client.map( - `${tableChain.getSelect({ surveyId, cycle, includeScript })} + `${tableChain.getSelect({ surveyId, includeScript })} ORDER BY ${tableChain.columnDateCreated} DESC LIMIT ${limit || 'ALL'} OFFSET ${offset}`, diff --git a/server/modules/analysis/repository/chain/update.js b/server/modules/analysis/repository/chain/update.js index 0375b59bfb..06acf2bc3b 100644 --- a/server/modules/analysis/repository/chain/update.js +++ b/server/modules/analysis/repository/chain/update.js @@ -1,6 +1,5 @@ import * as DB from '../../../../db' -import * as Chain from '@common/analysis/chain' import { TableChain } from '../../../../../common/model/db' /** @@ -9,7 +8,7 @@ import { TableChain } from '../../../../../common/model/db' * @param {!object} params - The query parameters. * @param {!string} params.surveyId - The survey id. * @param {!string} params.chainUuid - The processing chain uuid. - * @param {Object} [params.fields={}] - A object containing the fields to update. + * @param {{[key:string]: any}} [params.fields={}] - A object containing the fields to update. * @param {boolean} [params.dateExecuted=false] - Whether to update date executed to current time. * @param {boolean} [params.dateModified=false] - Whether to update date modified to current time. * @param {pgPromise.IDatabase} [client=db] - The database client. @@ -41,23 +40,3 @@ export const updateChain = async (params, client = DB.client) => { [chainUuid, ...Object.values(fields)] ) } - -/** - * Remove survey cycles from all processing chains. - * - * @param {!object} params - The query parameters. - * @param {!string} params.surveyId - The survey id. - * @param {!Array.} params.cycles - The cycles to remove. - * @param {pgPromise.IDatabase} [client=db] - The database client. - * - * @returns {Promise} - The result promise. - */ -export const removeChainCycles = async (params, client) => { - const { surveyId, cycles } = params - const tableChain = new TableChain(surveyId) - return client.none( - `UPDATE ${tableChain.nameQualified} - SET ${TableChain.columnSet.props} = jsonb_set(${TableChain.columnSet.props}, '{${Chain.keysProps.cycles}}', - (${TableChain.columnSet.props}->'${Chain.keysProps.cycles}') ${cycles.map((cycle) => `- '${cycle}'`).join(' ')})` - ) -} diff --git a/server/modules/analysis/service/chainSummaryGenerator.js b/server/modules/analysis/service/chainSummaryGenerator.js index 3fba57e9c1..f672bc9dd6 100644 --- a/server/modules/analysis/service/chainSummaryGenerator.js +++ b/server/modules/analysis/service/chainSummaryGenerator.js @@ -159,7 +159,6 @@ const generateChainSummary = async ({ surveyId, chainUuid, cycle, lang: langPara label: Chain.getLabel(lang, defaultLang)(chain), selectedLanguage: lang, selectedCycle: getCycleLabel(cycle), - cycles: Chain.getCycles(chain).map(getCycleLabel), samplingDesign: Chain.hasSamplingDesign(chain), baseUnit: NodeDef.getName(baseUnitNodeDef), baseUnitEntityKeys, diff --git a/server/modules/survey/service/publish/jobs/chainsCyclesCheckJob.js b/server/modules/survey/service/publish/jobs/chainsCyclesCheckJob.js index e6769f78b9..cff1a0ab10 100644 --- a/server/modules/survey/service/publish/jobs/chainsCyclesCheckJob.js +++ b/server/modules/survey/service/publish/jobs/chainsCyclesCheckJob.js @@ -6,7 +6,6 @@ import * as Survey from '@core/survey/survey' import * as SurveyManager from '@server/modules/survey/manager/surveyManager' import * as NodeDefManager from '@server/modules/nodeDef/manager/nodeDefManager' -import * as AnalysisManager from '@server/modules/analysis/manager' export default class ChainsCyclesCheckJob extends Job { constructor(params) { @@ -14,23 +13,10 @@ export default class ChainsCyclesCheckJob extends Job { } async execute() { - this.total = 3 - const { surveyId, tx } = this - const { cycleKeys, cycleKeysDeleted = [] } = await this._getCycleKeys() + const { cycleKeys } = await this._getCycleKeys() // 1. set all survey cycles to all nodeDef analysis await NodeDefManager.updateNodeDefAnalysisCycles(surveyId, cycleKeys, tx) - this.incrementProcessedItems() - - // 2. dissoc deleted survey cycles from processing chains - if (!R.isEmpty(cycleKeysDeleted)) { - await AnalysisManager.removeChainCycles({ surveyId, cycles: cycleKeysDeleted }, tx) - } - this.incrementProcessedItems() - - // 3. delete processing chains with no cycles - await AnalysisManager.deleteChainWithoutCycle({ surveyId }, tx) - this.incrementProcessedItems() } async _getCycleKeys() { diff --git a/webapp/components/form/Input/Input.js b/webapp/components/form/Input/Input.js index b532869fd4..c389170eea 100644 --- a/webapp/components/form/Input/Input.js +++ b/webapp/components/form/Input/Input.js @@ -128,15 +128,6 @@ Input.propTypes = { inputType: PropTypes.oneOf(['input', 'textarea']), maxLength: PropTypes.number, name: PropTypes.string, - onChange: PropTypes.func, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - placeholder: PropTypes.string, - readOnly: PropTypes.bool, - title: PropTypes.string, - type: PropTypes.oneOf(['text', 'number', 'password']), - validation: PropTypes.object, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), numberFormat: PropTypes.shape({ decimalScale: PropTypes.number, decimalSeparator: PropTypes.string, @@ -144,5 +135,14 @@ Input.propTypes = { maxLength: PropTypes.number, placeholder: PropTypes.string, }), + onChange: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + placeholder: PropTypes.string, + readOnly: PropTypes.bool, textTransformFunction: PropTypes.func, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + type: PropTypes.oneOf(['text', 'number', 'password']), + validation: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), } diff --git a/webapp/components/form/SimpleTextInput.js b/webapp/components/form/SimpleTextInput.js index 973c4c777a..b2d38e2812 100644 --- a/webapp/components/form/SimpleTextInput.js +++ b/webapp/components/form/SimpleTextInput.js @@ -99,7 +99,7 @@ SimpleTextInput.propTypes = { startAdornment: PropTypes.any, testId: PropTypes.string, textTransformFunction: PropTypes.func, - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), type: PropTypes.string, - value: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), } diff --git a/webapp/views/App/views/Analysis/Chain/ChainBasicProps.js b/webapp/views/App/views/Analysis/Chain/ChainBasicProps.js index 718a8652d1..a3d96bb81c 100644 --- a/webapp/views/App/views/Analysis/Chain/ChainBasicProps.js +++ b/webapp/views/App/views/Analysis/Chain/ChainBasicProps.js @@ -14,7 +14,6 @@ import * as API from '@webapp/service/api' import { FormItem } from '@webapp/components/form/Input' import { Checkbox } from '@webapp/components/form' -import CyclesSelector from '@webapp/components/survey/CyclesSelector' import LabelsEditor from '@webapp/components/survey/LabelsEditor' import { ChainRStudioFieldset } from './ChainRStudioFieldset' @@ -60,10 +59,6 @@ export const ChainBasicProps = (props) => { labels={chain.props.descriptions} onChange={(descriptions) => updateChain({ ...chain, props: { ...chain.props, descriptions } })} /> - updateChain({ ...chain, props: { ...chain.props, cycles } })} - /> { const navigate = useNavigate() - const surveyCycleKey = useSurveyCycleKey() const lang = useSurveyPreferredLang() const onRowClick = (chain) => navigate(`${appModuleUri(analysisModules.chain)}${Chain.getUuid(chain)}`) @@ -28,7 +27,6 @@ const ChainsView = () => { headerLeftComponent={HeaderLeft} module="processing-chains" onRowClick={onRowClick} - restParams={{ surveyCycleKey }} columns={[ { key: 'validation',