Skip to content

Commit

Permalink
Revert "3213 paneuropean linked nodes shows wrong data 1 (#3218)"
Browse files Browse the repository at this point in the history
This reverts commit eedd721.
  • Loading branch information
sorja authored Nov 13, 2023
1 parent eedd721 commit 79dc39d
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 150 deletions.
5 changes: 2 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ module.exports = {
testMatch: ['**/*.test.ts'],
moduleDirectories: ['node_modules'],
moduleNameMapper: {
'client/(.*)': '<rootDir>/client/$1',
'i18n/(.*)': '<rootDir>/i18n/$1',
'client/(.*)': '<rootDir>/client/$1',
'meta/(.*)': '<rootDir>/meta/$1',
'server/(.*)': '<rootDir>/server/$1',
'test/(.*)': '<rootDir>/test/$1',
'utils/objects(.*)': '<rootDir>/utils/objects$1',
'utils/arrays(.*)': '<rootDir>/utils/arrays$1',
'utils/dates(.*)': '<rootDir>/utils/dates$1',
'utils/functions(.*)': '<rootDir>/utils/functions$1',
'utils/numbers(.*)': '<rootDir>/utils/numbers$1',
'utils/objects(.*)': '<rootDir>/utils/objects$1',
'utils/promises(.*)': '<rootDir>/utils/promises$1',
'utils/strings(.*)': '<rootDir>/utils/strings$1',
'utils/uuids(.*)': '<rootDir>/utils/uuids$1',
},
Expand Down
13 changes: 6 additions & 7 deletions src/meta/assessment/assessmentMetaCache.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { AssessmentName } from 'meta/assessment/assessmentName'
import { ColName } from 'meta/assessment/col'
import { CycleName } from 'meta/assessment/cycle'
import { VariableName } from 'meta/assessment/row'
import { TableName } from 'meta/assessment/table'
import { AssessmentName } from './assessmentName'
import { CycleName } from './cycle'
import { VariableName } from './row'
import { TableName } from './table'

export interface VariableCache {
assessmentName?: AssessmentName
cycleName?: CycleName
tableName: TableName
variableName: VariableName
colName?: ColName // TODO: will colName become mandatory when handling dependencies by col ?
colName?: string // TODO: will colName become mandatory when handling dependencies by col ?
}

/**
Expand All @@ -25,7 +24,7 @@ export interface VariableCache {
*/
export type VariablesCache = Record<TableName, Record<VariableName, VariableCache>>

export type DependencyRecord = Record<TableName, Record<VariableName, Array<VariableCache>>>
export type DependencyRecord = Record<string, Record<string, Array<VariableCache>>>

export type DependencyCache = {
dependencies: DependencyRecord
Expand Down
13 changes: 10 additions & 3 deletions src/meta/expressionEvaluator/util/parseMemberVariable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { MemberExpression } from '@openforis/arena-core'

import { VariableCache } from 'meta/assessment'

const getExpressionDepth = (expressionNode: MemberExpression): number => {
let depth = 0
let currentExpressionNode = expressionNode
Expand All @@ -13,7 +11,16 @@ const getExpressionDepth = (expressionNode: MemberExpression): number => {
return depth
}

export const parseMemberVariable = (expressionNode: MemberExpression): VariableCache => {
export const parseMemberVariable = (
expressionNode: MemberExpression
): {
tableName: string
variableName: string
colName?: string
assessmentName?: string
cycleName?: string
depth?: number
} => {
const depth = getExpressionDepth(expressionNode)

switch (depth) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ExpressionContext } from '@openforis/arena-core'

import { Assessment, AssessmentName, CycleName, RowCache } from 'meta/assessment'
import { AssessmentMetaCache, Row } from 'meta/assessment'

export interface Context extends ExpressionContext {
assessments: Array<Assessment>
assessmentName: AssessmentName
cycleName: CycleName
row: RowCache
assessmentMetaCache: AssessmentMetaCache
row: Row
tableName: string
type: 'calculations' | 'validations'
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ExpressionNodeEvaluator, MemberExpression } from '@openforis/arena-core'
import { Objects } from 'utils/objects'

import { AssessmentMetaCaches, RowCache, VariableCache } from 'meta/assessment'
import { Row, VariableCache } from 'meta/assessment'
import { ExpressionEvaluator } from 'meta/expressionEvaluator'

import { Context } from './context'

const _includesVariableCache = (variables: Array<VariableCache>, variable: VariableCache): boolean =>
const includesVariableCache = (variables: Array<VariableCache>, variable: VariableCache): boolean =>
Boolean(
variables.find(
(v) =>
Expand All @@ -17,69 +16,56 @@ const _includesVariableCache = (variables: Array<VariableCache>, variable: Varia
)
)

const _excludeDependant = (row: RowCache, tableName: string, variableName: string): boolean =>
const excludeDependant = (row: Row, tableName: string, variableName: string): boolean =>
Boolean(row.props?.dependantsExclude?.find((v) => v.tableName === tableName && v.variableName === variableName))

export class MemberEvaluator extends ExpressionNodeEvaluator<Context, MemberExpression> {
evaluate(expressionNode: MemberExpression): string {
const memberVariable = ExpressionEvaluator.parseMemberVariable(expressionNode)

this.#addDependant(memberVariable)
this.#addDependency(memberVariable)
const { assessmentMetaCache, row, tableName, type } = this.context

return `${memberVariable.tableName}.${memberVariable.variableName}`
}

#addDependant(variable: VariableCache): void {
const { assessments, assessmentName, cycleName, row, type } = this.context

const assessment = assessments.find((a) => a.props.name === (variable.assessmentName ?? assessmentName))
const cycle = assessment.cycles.find((c) => c.name === (variable.cycleName ?? cycleName))
const metaCache = AssessmentMetaCaches.getMetaCache({ assessment, cycle })
const variablesCache = AssessmentMetaCaches.getVariablesByTables({ assessment, cycle })
const memberVariable = ExpressionEvaluator.parseMemberVariable(expressionNode)

if (variablesCache[variable.tableName] && !_excludeDependant(row, variable.tableName, variable.variableName)) {
const propsDependants = { assessment, cycle, tableName: variable.tableName, variableName: variable.variableName }
const dependants =
type === 'calculations'
? AssessmentMetaCaches.getCalculationsDependants(propsDependants)
: AssessmentMetaCaches.getValidationsDependants(propsDependants)
const dependant: VariableCache = {
assessmentName: assessmentName !== assessment.props.name ? assessmentName : undefined,
cycleName: cycleName !== cycle.name ? cycleName : undefined,
tableName: row.tableName,
variableName: row.props.variableName,
if (assessmentMetaCache.variablesByTable[memberVariable.tableName]) {
const dependantTable = assessmentMetaCache[type].dependants?.[memberVariable.tableName] ?? {}
const dependants = dependantTable[memberVariable.variableName] ?? []
const dependant: VariableCache = { variableName: row.props.variableName, tableName }
if (
!excludeDependant(row, memberVariable.tableName, memberVariable.variableName) &&
!includesVariableCache(dependants, dependant)
) {
assessmentMetaCache[type].dependants = {
...assessmentMetaCache[type].dependants,
[memberVariable.tableName]: {
...dependantTable,
[memberVariable.variableName]: [...dependants, dependant],
},
}
}

if (!_includesVariableCache(dependants, dependant)) {
const path = [type, 'dependants', variable.tableName, variable.variableName]
Objects.setInPath({ obj: metaCache, path, value: [...dependants, dependant] })
const dependencyTable = assessmentMetaCache[type].dependencies?.[tableName] ?? {}
const dependencies = dependencyTable[row.props.variableName] ?? []
const dependency: VariableCache = {
variableName: memberVariable.variableName,
tableName: memberVariable.tableName,
}
}
}

#addDependency(variable: VariableCache): void {
const { assessments, assessmentName, cycleName, row, type } = this.context

const assessment = assessments.find((a) => a.props.name === assessmentName)
const cycle = assessment.cycles.find((c) => c.name === cycleName)
const metaCache = AssessmentMetaCaches.getMetaCache({ assessment, cycle })
const variablesCache = AssessmentMetaCaches.getVariablesByTables({ assessment, cycle })
const { tableName } = row
const { variableName } = row.props

// 1. parse internal dependencies
if (variablesCache[tableName]) {
const propsDependency = { assessment, cycle, tableName, variableName }
const dependencies =
type === 'calculations'
? AssessmentMetaCaches.getCalculationsDependencies(propsDependency)
: AssessmentMetaCaches.getValidationsDependencies(propsDependency)
if (memberVariable.assessmentName && memberVariable.cycleName) {
dependency.assessmentName = memberVariable.assessmentName
dependency.cycleName = memberVariable.cycleName
}

if (!_includesVariableCache(dependencies, variable)) {
const path = [type, 'dependencies', tableName, variableName]
Objects.setInPath({ obj: metaCache, path, value: [...dependencies, variable] })
if (!includesVariableCache(dependencies, dependency)) {
assessmentMetaCache[type].dependencies = {
...assessmentMetaCache[type].dependencies,
[tableName]: {
...dependencyTable,
[row.props.variableName]: [...dependencies, dependency],
},
}
}

return `${tableName}.${memberVariable.variableName}`
}
return `${memberVariable.tableName}.${memberVariable.variableName}`
}
}
124 changes: 48 additions & 76 deletions src/server/controller/assessment/generateMetaCache/index.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,72 @@
import { Objects } from 'utils/objects'
import { Promises } from 'utils/promises'
import { Assessment, AssessmentMetaCache, Cycle } from 'meta/assessment'

import { AssessmentMetaCache, AssessmentName, RowCache } from 'meta/assessment'

import { BaseProtocol, DB } from 'server/db'
import { AssessmentRepository } from 'server/repository/assessment/assessment'
import { BaseProtocol } from 'server/db'
import { RowRepository } from 'server/repository/assessment/row'
import { ValueAggregateRepository } from 'server/repository/assessmentCycle/valueAggregate'

import { DependencyEvaluator } from './dependencyEvaluator'

/**
* This method generates meta cache for all assessments
*/
export const generateMetaCache = async (client: BaseProtocol = DB): Promise<void> => {
// 1. init assessments meta cache and rows
const assessments = await AssessmentRepository.getAll({}, client)
const rows: Record<AssessmentName, Array<RowCache>> = {}
await Promises.each(assessments, async (assessment) => {
rows[assessment.props.name] = (await RowRepository.getManyCache({ assessment }, client)).filter(
(row) =>
Boolean(row.props.validateFns || row.props.calculateFn) ||
Boolean(row.cols.find((col) => Boolean(col.props.validateFns || col.props.calculateFn)))
)
type Props = {
assessment: Assessment
cycle: Cycle
}

// init cycle meta cache
await Promises.each(assessment.cycles, async (cycle) => {
const [variables, valueAggregate] = await Promise.all([
RowRepository.getVariablesCache({ assessment, cycle }, client),
ValueAggregateRepository.getVariablesCache({ assessment, cycle }, client),
])
const metaCache: AssessmentMetaCache = {
calculations: { dependants: {}, dependencies: {} },
validations: { dependants: {}, dependencies: {} },
variablesByTable: { ...variables, ...valueAggregate },
}
Objects.setInPath({ obj: assessment, path: ['metaCache', cycle.uuid], value: metaCache })
})
})
export const generateMetaCache = async (props: Props, client: BaseProtocol): Promise<void> => {
const { assessment, cycle } = props

// 2. generate assessments meta cache
assessments.forEach((assessment) => {
const assessmentName = assessment.props.name
const [variables, valueAggregate] = await Promise.all([
RowRepository.getVariablesCache({ assessment, cycle }, client),
ValueAggregateRepository.getVariablesCache({ assessment, cycle }, client),
])

assessment.cycles.forEach((cycle) => {
const cycleName = cycle.name
const assessmentMetaCache: AssessmentMetaCache = {
calculations: { dependants: {}, dependencies: {} },
validations: { dependants: {}, dependencies: {} },
variablesByTable: { ...variables, ...valueAggregate },
}

rows[assessmentName].forEach((row) => {
const context = { assessments, assessmentName, cycleName, row }
const rows = (await RowRepository.getManyCache({ assessment }, client)).filter(
(row) =>
Boolean(row.props.validateFns || row.props.calculateFn) ||
Boolean(row.cols.find((col) => Boolean(col.props.validateFns || col.props.calculateFn)))
)

if (row.props.calculateFn?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(row.props.calculateFn[cycle.uuid], { ...context, type: 'calculations' })
if (row.props.calculateIf?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(row.props.calculateIf[cycle.uuid], {
...context,
type: 'calculations',
})
}
} else {
row.cols.forEach((col) => {
if (col.props.calculateFn?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(col.props.calculateFn[cycle.uuid], {
...context,
type: 'calculations',
})
}
})
rows.forEach(({ tableName, ...row }) => {
const context = { row, tableName, assessmentMetaCache }
if (row.props.calculateFn?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(row.props.calculateFn[cycle.uuid], { ...context, type: 'calculations' })
if (row.props.calculateIf?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(row.props.calculateIf[cycle.uuid], { ...context, type: 'calculations' })
}
} else {
row.cols.forEach((col) => {
if (col.props.calculateFn?.[cycle.uuid]) {
DependencyEvaluator.evalDependencies(col.props.calculateFn[cycle.uuid], { ...context, type: 'calculations' })
}
})
}

if (row.props.validateFns?.[cycle.uuid]) {
row.props.validateFns[cycle.uuid].forEach((validateFn) =>
if (row.props.validateFns?.[cycle.uuid]) {
row.props.validateFns[cycle.uuid].forEach((validateFn) =>
DependencyEvaluator.evalDependencies(validateFn, { ...context, type: 'validations' })
)
} else {
row.cols.forEach((col) => {
if (col.props.validateFns?.[cycle.uuid]) {
col.props.validateFns?.[cycle.uuid].forEach((validateFn) => {
DependencyEvaluator.evalDependencies(validateFn, { ...context, type: 'validations' })
)
} else {
row.cols.forEach((col) => {
if (col.props.validateFns?.[cycle.uuid]) {
col.props.validateFns?.[cycle.uuid].forEach((validateFn) => {
DependencyEvaluator.evalDependencies(validateFn, { ...context, type: 'validations' })
})
}
})
}
})
})
}
})

await Promise.all(
assessments.map((assessment) =>
client.query<void>(
`
return client.query(
`
update assessment
set meta_cache = $1::jsonb
set meta_cache = jsonb_set(meta_cache, '{${cycle.uuid}}', $1::jsonb)
where id = $2
`,
[assessment.metaCache, assessment.id]
)
)
[JSON.stringify(assessmentMetaCache), assessment.id]
)
}
15 changes: 14 additions & 1 deletion src/tools/generateMetaCache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@ import { AssessmentController } from 'server/controller/assessment'
import { DB } from 'server/db'
import { Logger } from 'server/utils/logger'

const client = DB

const close = async () => {
await DB.$pool.end()
}

const exec = async () => {
await AssessmentController.generateMetaCache()
const assessments = await AssessmentController.getAll({ metaCache: true }, client)
await Promise.all(
assessments.map((assessment) => {
Logger.debug(`\t---- Generating meta cache for assessment ${assessment.props.name}`)
return Promise.all(
assessment.cycles.map(async (cycle) => {
await AssessmentController.generateMetaCache({ assessment, cycle }, client)
Logger.debug(`\t\t----\tGenerated meta cache for cycle ${assessment.props.name}-${cycle.name}`)
})
)
})
)
await close()
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/objects/setInPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const setInPath = (params: { obj: any; path: string[]; value: any; exclud
if (i === path.length - 1) {
objCurrent[pathPart] = value
} else {
if (!objCurrent[pathPart]) {
if (!Object.prototype.hasOwnProperty.call(objCurrent, pathPart)) {
objCurrent[pathPart] = {}
}
objCurrent = objCurrent[pathPart]
Expand Down

0 comments on commit 79dc39d

Please sign in to comment.