diff --git a/core/survey/_survey/surveyInfo.js b/core/survey/_survey/surveyInfo.js index 0e68184bc3..d172347a32 100644 --- a/core/survey/_survey/surveyInfo.js +++ b/core/survey/_survey/surveyInfo.js @@ -17,6 +17,7 @@ export const keys = { published: ObjectUtils.keys.published, authGroups: 'authGroups', props: ObjectUtils.keys.props, + rdbInitialized: 'rdbInitialized', // Props collectUri: 'collectUri', collectReport: 'collectReport', @@ -52,6 +53,8 @@ export const status = { export const getInfo = (survey) => (survey.info ? survey.info : survey) // backwards compatibility: survey info were associated to 'info' prop +export const isRdbInitialized = R.propOr(false, keys.rdbInitialized) + // ====== READ surveyInfo export const { getId, getUuid, getProps, getPropsDraft, isPublished, getDescription, getDescriptions, getLabels } = ObjectUtils @@ -156,6 +159,8 @@ export const markDraft = R.assoc(keys.draft, true) export const assocSrs = (srs) => ObjectUtils.setProp(keys.srs, srs) +export const assocRDBInitilized = R.assoc(keys.rdbInitialized) + // ====== UTILS export const isValid = (surveyInfo) => surveyInfo && surveyInfo.id diff --git a/core/survey/nodeDefLayout.js b/core/survey/nodeDefLayout.js index c3fdaa6f58..7607fd8556 100644 --- a/core/survey/nodeDefLayout.js +++ b/core/survey/nodeDefLayout.js @@ -92,9 +92,11 @@ export const getLayoutChildrenCompressed = : // item in another row, can have the same x of the previous one Math.min(itemPrev.x, xOriginal) + const prevYDiff = itemPrev.yOriginal - itemPrev.y + const y = sameRowOfPreviousItem ? // item can have the same y of the previous one - Math.min(itemPrev.y, yOriginal) + yOriginal - prevYDiff : // item in another row, move it yPrev + hPrev Math.min(itemPrev.y + itemPrev.h, yOriginal) diff --git a/core/survey/survey.js b/core/survey/survey.js index 526e4c9283..e1a7da15f8 100644 --- a/core/survey/survey.js +++ b/core/survey/survey.js @@ -121,6 +121,7 @@ export const { isDraft, isValid, isFromCollect, + isRdbInitialized, getCollectUri, getCollectReport, getCollectNodeDefsInfoByPath, @@ -135,7 +136,7 @@ export const { export const { getAuthGroupByName, getAuthGroups, isAuthGroupAdmin, getAuthGroupAdmin } = SurveyInfo // UPDATE -export const { assocAuthGroups, assocSrs, markDraft } = SurveyInfo +export const { assocAuthGroups, assocRDBInitilized, assocSrs, markDraft } = SurveyInfo // ====== READ nodeDefs export const { @@ -149,6 +150,7 @@ export const { getNodeDefChildren, getNodeDefChildrenInOwnPage, hasNodeDefChildrenEntities, + getNodeDefChildrenSorted, getNodeDefChildByName, getNodeDefSiblingByName, getNodeDefByName, diff --git a/server/modules/nodeDef/repository/nodeDefRepository.js b/server/modules/nodeDef/repository/nodeDefRepository.js index c571aea7a8..78791da969 100644 --- a/server/modules/nodeDef/repository/nodeDefRepository.js +++ b/server/modules/nodeDef/repository/nodeDefRepository.js @@ -71,13 +71,12 @@ export const insertNodeDef = async (surveyId, nodeDef, client = DB) => (row) => dbTransformCallback({ row, draft: true, advanced: true }) // Always loading draft when creating or updating a nodeDef ) -export const insertNodeDefsBatch = async ({ surveyId, nodeDefs, backup = false }, client = DB) => - client.tx(async (tx) => { - const schema = getSurveyDBSchema(surveyId) - await tx.batch([ - nodeDefs.map((nodeDef) => - tx.none( - ` +export const insertNodeDefsBatch = async ({ surveyId, nodeDefs, backup = false }, client = DB) => { + const schema = getSurveyDBSchema(surveyId) + return client.batch([ + nodeDefs.map((nodeDef) => + client.none( + ` INSERT INTO ${schema}.node_def ( parent_uuid, uuid, @@ -93,22 +92,22 @@ export const insertNodeDefsBatch = async ({ surveyId, nodeDefs, backup = false } $4::jsonb, $5::jsonb, $6::jsonb, $7::jsonb, $8,$9,$10)`, - [ - NodeDef.getParentUuid(nodeDef), - nodeDef.uuid, - NodeDef.getType(nodeDef), - backup ? NodeDef.getProps(nodeDef) : {}, - backup ? NodeDef.getPropsDraft(nodeDef) : NodeDef.getProps(nodeDef), - backup ? NodeDef.getPropsAdvanced(nodeDef) : {}, - backup ? NodeDef.getPropsAdvancedDraft(nodeDef) : NodeDef.getPropsAdvanced(nodeDef), - NodeDef.getMeta(nodeDef), - NodeDef.isAnalysis(nodeDef), - NodeDef.isVirtual(nodeDef), - ] - ) - ), - ]) - }) + [ + NodeDef.getParentUuid(nodeDef), + nodeDef.uuid, + NodeDef.getType(nodeDef), + backup ? NodeDef.getProps(nodeDef) : {}, + backup ? NodeDef.getPropsDraft(nodeDef) : NodeDef.getProps(nodeDef), + backup ? NodeDef.getPropsAdvanced(nodeDef) : {}, + backup ? NodeDef.getPropsAdvancedDraft(nodeDef) : NodeDef.getPropsAdvanced(nodeDef), + NodeDef.getMeta(nodeDef), + NodeDef.isAnalysis(nodeDef), + NodeDef.isVirtual(nodeDef), + ] + ) + ), + ]) +} // ============== READ diff --git a/server/modules/survey/manager/surveyManager.js b/server/modules/survey/manager/surveyManager.js index 0c4f19461c..62e1c37cd2 100644 --- a/server/modules/survey/manager/surveyManager.js +++ b/server/modules/survey/manager/surveyManager.js @@ -13,6 +13,7 @@ import * as User from '@core/user/user' import * as ObjectUtils from '@core/objectUtils' import * as Validation from '@core/validation/validation' import * as PromiseUtils from '@core/promiseUtils' +import SystemError from '@core/systemError' import { db } from '@server/db/db' import { DBMigrator } from '@openforis/arena-server' @@ -32,7 +33,6 @@ import * as UserManager from '@server/modules/user/manager/userManager' import * as UserRepository from '@server/modules/user/repository/userRepository' import * as SurveyRepositoryUtils from '../repository/surveySchemaRepositoryUtils' import * as SurveyRepository from '../repository/surveyRepository' -import SystemError from '@core/systemError' const assocSurveyInfo = (survey) => survey @@ -44,6 +44,17 @@ const _fetchAndAssocSrss = async ({ surveyInfo }, client) => { return Survey.assocSrs(srss)(surveyInfo) } +const _fetchAndAssocRdbInitialized = async ({ surveyInfo }, client) => { + const surveyId = Survey.getId(surveyInfo) + const rdbInitialized = await SchemaRdbRepository.selectSchemaExists(surveyId, client) + return Survey.assocRDBInitilized(rdbInitialized)(surveyInfo) +} + +const _fetchAndAssocAdditionalInfo = async ({ surveyInfo }, client) => { + let surveyInfoUpdated = await _fetchAndAssocSrss({ surveyInfo }, client) + return _fetchAndAssocRdbInitialized({ surveyInfo: surveyInfoUpdated }, client) +} + // ====== VALIDATION export const validateNewSurvey = async ({ newSurvey }) => { @@ -168,7 +179,7 @@ export const importSurvey = async (params, client = db) => { surveyInfo ) - surveyInfo = await _fetchAndAssocSrss({ surveyInfo }, t) + surveyInfo = await _fetchAndAssocAdditionalInfo({ surveyInfo }, t) await _addUserToSurveyAdmins({ user, surveyInfo }, t) @@ -193,7 +204,7 @@ export const fetchSurveyById = async ({ surveyId, draft = false, validate = fals ]) let surveyInfoUpdated = Survey.assocAuthGroups(authGroups)(surveyInfo) - surveyInfoUpdated = await _fetchAndAssocSrss({ surveyInfo: surveyInfoUpdated }, client) + surveyInfoUpdated = await _fetchAndAssocAdditionalInfo({ surveyInfo: surveyInfoUpdated }, client) const validation = validate ? await validateSurveyInfo(surveyInfoUpdated) : null diff --git a/server/modules/surveyRdb/repository/schemaRdbRepository.js b/server/modules/surveyRdb/repository/schemaRdbRepository.js index 7ae9178a97..bff3ef03ba 100644 --- a/server/modules/surveyRdb/repository/schemaRdbRepository.js +++ b/server/modules/surveyRdb/repository/schemaRdbRepository.js @@ -1,9 +1,21 @@ -import * as SchemaRdb from '@common/surveyRdb/schemaRdb' +import { Schemata } from '@common/model/db' import { db } from '@server/db/db' export const dropSchema = async (surveyId, client = db) => - client.query(`DROP SCHEMA IF EXISTS ${SchemaRdb.getName(surveyId)} CASCADE`) + client.query(`DROP SCHEMA IF EXISTS ${Schemata.getSchemaSurveyRdb(surveyId)} CASCADE`) export const createSchema = async (surveyId, client = db) => - client.query(`CREATE SCHEMA ${SchemaRdb.getName(surveyId)}`) + client.query(`CREATE SCHEMA ${Schemata.getSchemaSurveyRdb(surveyId)}`) + +export const selectSchemaExists = async (surveyId, client = db) => { + const result = await client.one( + ` + SELECT COUNT(*) = 1 AS res + FROM information_schema.schemata + WHERE schema_name = $1 + `, + [Schemata.getSchemaSurveyRdb(surveyId)] + ) + return result.res +} diff --git a/webapp/components/Table/Header/VisibleColumnsMenu/VisibleColumnsMenu.js b/webapp/components/Table/Header/VisibleColumnsMenu/VisibleColumnsMenu.js index f4d98ee4f3..e613571b40 100644 --- a/webapp/components/Table/Header/VisibleColumnsMenu/VisibleColumnsMenu.js +++ b/webapp/components/Table/Header/VisibleColumnsMenu/VisibleColumnsMenu.js @@ -28,6 +28,7 @@ export const VisibleColumnsMenu = (props) => { return ( { diff --git a/webapp/components/expression/expressionEditor.js b/webapp/components/expression/expressionEditor.js index 2ada056b63..bd6e80adc8 100644 --- a/webapp/components/expression/expressionEditor.js +++ b/webapp/components/expression/expressionEditor.js @@ -5,14 +5,13 @@ import PropTypes from 'prop-types' import * as R from 'ramda' import * as Expression from '@core/expressionParser/expression' -import * as Survey from '@core/survey/survey' import * as NodeDef from '@core/survey/nodeDef' import { TestId } from '@webapp/utils/testId' import { useI18n } from '@webapp/store/system' import ExpressionEditorPopup from './expressionEditorPopup' import { ExpressionEditorType } from './expressionEditorType' -import { useSurvey, useSurveyPreferredLang } from '@webapp/store/survey' +import { useNodeDefByUuid } from '@webapp/store/survey' import { Button } from '../buttons' const ExpressionEditor = (props) => { @@ -33,8 +32,7 @@ const ExpressionEditor = (props) => { } = props const i18n = useI18n() - const survey = useSurvey() - const lang = useSurveyPreferredLang() + const nodeDefCurrent = useNodeDefByUuid(nodeDefUuidCurrent) const [edit, setEdit] = useState(false) @@ -54,11 +52,10 @@ const ExpressionEditor = (props) => { const idPrefix = `expression-editor-${placeholder ? 'placeholder' : index}-${qualifier}` const qualifierLabel = i18n.t(`expressionEditor.qualifier.${qualifier}`) - const nodeDefCurrent = Survey.getNodeDefByUuid(nodeDefUuidCurrent)(survey) const popupHeader = nodeDefCurrent ? i18n.t('expressionEditor.header.editingExpressionForNodeDefinition', { qualifier: qualifierLabel, - nodeDef: NodeDef.getLabel(nodeDefCurrent, lang), + nodeDef: NodeDef.getName(nodeDefCurrent), }) : null diff --git a/webapp/components/survey/SurveyDefsLoader/SurveyDefsLoader.js b/webapp/components/survey/SurveyDefsLoader/SurveyDefsLoader.js index e8a261865f..47642cd7a8 100644 --- a/webapp/components/survey/SurveyDefsLoader/SurveyDefsLoader.js +++ b/webapp/components/survey/SurveyDefsLoader/SurveyDefsLoader.js @@ -42,7 +42,11 @@ const SurveyDefsLoader = (props) => { return null } - if (!requirePublish || Survey.isPublished(surveyInfo) || Survey.isFromCollect(surveyInfo)) { + if ( + !requirePublish || + Survey.isPublished(surveyInfo) || + (Survey.isFromCollect(surveyInfo) && Survey.isRdbInitialized(surveyInfo)) + ) { return children } diff --git a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntitySwitch.js b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntitySwitch.js index 914a1eefb5..517360796a 100644 --- a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntitySwitch.js +++ b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntitySwitch.js @@ -2,7 +2,6 @@ import './nodeDefEntitySwitch.scss' import React from 'react' -import * as A from '@core/arena' import * as Survey from '@core/survey/survey' import * as NodeDef from '@core/survey/nodeDef' import * as NodeDefLayout from '@core/survey/nodeDefLayout' @@ -20,20 +19,20 @@ const componentsByRenderType = { const NodeDefEntitySwitch = (props) => { const { surveyCycleKey, nodeDef } = props + const survey = useSurvey() + const renderType = NodeDefLayout.getRenderType(surveyCycleKey)(nodeDef) if (!renderType) { // node def not in current cycle return null } - const survey = useSurvey() - const includeAnalysis = false - const childDefs = Survey.getNodeDefChildren(props.nodeDef, includeAnalysis)(survey) + const childDefs = Survey.getNodeDefChildrenSorted({ nodeDef, includeAnalysis, cycle: surveyCycleKey })(survey) const nodeDefName = NodeDef.getName(nodeDef) const childUuids = NodeDefLayout.getLayoutChildrenUuids(surveyCycleKey)(nodeDef) - const childNames = childUuids.map((childUuid) => A.pipe(Survey.getNodeDefByUuid(childUuid), NodeDef.getName)(survey)) + const childNames = Survey.getNodeDefsByUuids(childUuids)(survey).map(NodeDef.getName) return (
dispatch(RecordActions.createRecord(navigate))} - className="btn-s" iconClassName="icon-plus icon-12px icon-left" label="common.new" /> diff --git a/webapp/views/App/views/Data/Records/HeaderLeft/HeaderLeft.scss b/webapp/views/App/views/Data/Records/HeaderLeft/HeaderLeft.scss index 77c8fd395e..6910f71351 100644 --- a/webapp/views/App/views/Data/Records/HeaderLeft/HeaderLeft.scss +++ b/webapp/views/App/views/Data/Records/HeaderLeft/HeaderLeft.scss @@ -1,16 +1,22 @@ @import 'webapp/style/vars'; -$tableHeaderHeight: 40px; +$tableHeaderHeight: 30px; .records__header-left { display: flex; + align-items: center; height: $tableHeaderHeight; column-gap: 1rem; -} -.records__header-left__input-search { - padding: 0px 5px; - border: 1px solid $greyBorder; - background: white; - width: 15rem; + button, + .btn { + padding: 0.5rem 0.8rem; + } + + .records__header-left__input-search { + padding: 5px 5px; + border: 1px solid $greyBorder; + background: white; + width: 15rem; + } } diff --git a/webapp/views/App/views/Data/Records/Records.js b/webapp/views/App/views/Data/Records/Records.js index d1eff35e36..9d5e71204b 100644 --- a/webapp/views/App/views/Data/Records/Records.js +++ b/webapp/views/App/views/Data/Records/Records.js @@ -12,6 +12,7 @@ import { useSurveyCycleKey, useNodeDefRootKeys } from '@webapp/store/survey' import { appModuleUri, dataModules } from '@webapp/app/appModules' import Table from '@webapp/components/Table' +import { LoadingBar } from '@webapp/components' import { useOnWebSocketEvent } from '@webapp/components/hooks' import HeaderLeft from './HeaderLeft' @@ -44,6 +45,10 @@ const Records = () => { eventHandler: onRecordsUpdate, }) + if (columns === null) { + return + } + return ( [ + return useMemo(() => { + if (categoryItemsByCodeDefUuid === null) return null + return [ { key: 'selected', renderItem: ({ itemSelected }) => ( @@ -56,6 +57,7 @@ export const useColumns = ({ categoryItemsByCodeDefUuid, navigateToRecord, onRec {itemPosition} ), + width: '3rem', }, ...nodeDefKeys.map((nodeDef) => ({ key: NodeDef.getUuid(nodeDef), @@ -83,12 +85,14 @@ export const useColumns = ({ categoryItemsByCodeDefUuid, navigateToRecord, onRec header: 'common.dateCreated', sortable: true, renderItem: ({ item: record }) => DateUtils.formatDateTimeDisplay(Record.getDateCreated(record)), + width: '11rem', }, { key: Record.keys.dateModified, header: 'common.dateLastModified', sortable: true, renderItem: ({ item: record }) => DateUtils.formatDateTimeDisplay(Record.getDateModified(record)), + width: '11rem', }, { key: Record.keys.ownerName, @@ -100,16 +104,19 @@ export const useColumns = ({ categoryItemsByCodeDefUuid, navigateToRecord, onRec header: 'dataView.records.step', sortable: true, renderItem: ({ item: record }) => Record.getStep(record), + width: '5rem', }, { key: 'errors', header: 'common.error_plural', renderItem: ({ item: record }) => A.pipe(Validation.getValidation, Validation.getErrorsCount)(record), + width: '6rem', }, { key: 'warnings', header: 'common.warning_plural', renderItem: ({ item: record }) => A.pipe(Validation.getValidation, Validation.getWarningsCount)(record), + width: '6rem', }, { key: 'action-buttons', @@ -134,8 +141,8 @@ export const useColumns = ({ categoryItemsByCodeDefUuid, navigateToRecord, onRec ) }, + width: '80px', }, - ], - [categoryItemsByCodeDefUuid, lang, nodeDefKeys, onRecordEditButtonClick, onRecordsUpdate, user] - ) + ] + }, [categoryItemsByCodeDefUuid, lang, nodeDefKeys, onRecordEditButtonClick, onRecordsUpdate, user]) } diff --git a/webapp/views/App/views/Data/Records/useNodeDefKeysCategoryItemsInLevel.js b/webapp/views/App/views/Data/Records/useNodeDefKeysCategoryItemsInLevel.js index 97176320e1..1c2b82ee79 100644 --- a/webapp/views/App/views/Data/Records/useNodeDefKeysCategoryItemsInLevel.js +++ b/webapp/views/App/views/Data/Records/useNodeDefKeysCategoryItemsInLevel.js @@ -8,7 +8,7 @@ import { useSurvey } from '@webapp/store/survey' export const useNodeDefKeysCategoryItemsInLevel = () => { const survey = useSurvey() - const [result, setResult] = useState({}) + const [result, setResult] = useState(null) useEffect(() => { const categoryItemsByCodeDefUuid = {} @@ -18,9 +18,13 @@ export const useNodeDefKeysCategoryItemsInLevel = () => { const surveyId = Survey.getId(survey) const nodeDefCodeKeys = nodeDefKeys.filter(NodeDef.isCode) + if (nodeDefCodeKeys.length === 0) { + setResult({}) + return + } nodeDefCodeKeys.forEach((nodeDefKey) => { const categoryUuid = NodeDef.getCategoryUuid(nodeDefKey) - if (!categoryUuid) return null // category not specified (e.g. error importing survey from Collect) + if (!categoryUuid) return // category not specified (e.g. error importing survey from Collect) const levelIndex = Survey.getNodeDefCategoryLevelIndex(nodeDefKey)(survey) const { request, cancel } = API.fetchCategoryItemsInLevelRequest({ @@ -29,10 +33,12 @@ export const useNodeDefKeysCategoryItemsInLevel = () => { levelIndex, draft: false, }) - requestCancelByNodeDefUuid[NodeDef.getUuid] = cancel + const nodeDefKeyUuid = NodeDef.getUuid(nodeDefKey) + requestCancelByNodeDefUuid[nodeDefKeyUuid] = cancel request.then(({ data }) => { + delete requestCancelByNodeDefUuid[nodeDefKeyUuid] const { items } = data - categoryItemsByCodeDefUuid[NodeDef.getUuid(nodeDefKey)] = items + categoryItemsByCodeDefUuid[nodeDefKeyUuid] = items if (Object.values(categoryItemsByCodeDefUuid).length === nodeDefCodeKeys.length) { setResult(categoryItemsByCodeDefUuid) }