diff --git a/src/client/pages/DataExport/DataExport.tsx b/src/client/pages/DataExport/DataExport.tsx index 424798929a..d850b0df71 100644 --- a/src/client/pages/DataExport/DataExport.tsx +++ b/src/client/pages/DataExport/DataExport.tsx @@ -44,7 +44,7 @@ const DataExport: React.FC = () => { const table = tables?.find((table) => table.props.dataExport) if (table) { tableName = table.props.name - rows = table.rows.filter((row) => !!row.props.variableName) + rows = table.rows.filter((row) => !!row.props.variableName && !row.props.excludeFromDataExport?.[cycle.uuid]) columns = table.props.columnsExport[cycle.uuid] ?? table.props.columnNames[cycle.uuid] } diff --git a/src/client/pages/DataExport/ResultsTable/ResultsTable.tsx b/src/client/pages/DataExport/ResultsTable/ResultsTable.tsx index b0753d1f6d..45b7089fe7 100644 --- a/src/client/pages/DataExport/ResultsTable/ResultsTable.tsx +++ b/src/client/pages/DataExport/ResultsTable/ResultsTable.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { Objects } from 'utils/objects' import { Areas, CountryIso } from 'meta/area' -import { Unit } from 'meta/dataExport' +import { Unit } from 'meta/assessment' import { useCycle } from 'client/store/assessment' import { useTableSections } from 'client/store/metadata' @@ -32,7 +32,9 @@ const ResultsTable: React.FC<{ tableName: string }> = ({ tableName }) => { const baseUnit = table?.props?.unit const columns = selection.sections[sectionName].columns ?? [] - const columnsAlwaysExport = table?.props?.columnsExportAlways[cycle.uuid] ?? [] + const cellsExportAlways = table?.props?.cellsExportAlways?.[cycle.uuid] ?? [] + const columnsAlwaysExport = table?.props?.columnsExportAlways?.[cycle.uuid] ?? [] + const columnsResults = [...columnsAlwaysExport, ...columns] const { variables } = selection.sections[sectionName] @@ -44,11 +46,12 @@ const ResultsTable: React.FC<{ tableName: string }> = ({ tableName }) => { }) const [units, setUnits] = useState>(initialUnits) const { results, resultsLoading } = useFetchResults({ - columnsAlwaysExport, - tableName, - sectionName, assessmentName, + cellsExportAlways, + columnsAlwaysExport, cycleName, + sectionName, + tableName, }) const onUnitChange = (value: Unit, variable: string) => { @@ -77,24 +80,43 @@ const ResultsTable: React.FC<{ tableName: string }> = ({ tableName }) => {
- + {cellsExportAlways.map((cell) => { + const { columnName, unit, variableName } = cell + return ( +
+ + {unit !== null && ( + + )} + {unit === null && + getColumnLabelKeys(String(columnName), sectionName, assessmentName).map( + (key) => `${i18n.t(key)} ` + )} + </th> + ) + })} {variables.map((variable) => ( <th key={variable} className="fra-table__header-cell" colSpan={columnsResults.length}> <Title baseUnit={baseUnit} - variable={variable} onUnitChange={onUnitChange} resultsLoading={resultsLoading} + variable={variable} /> </th> ))} @@ -121,17 +143,37 @@ const ResultsTable: React.FC<{ tableName: string }> = ({ tableName }) => { <th className="fra-table__category-cell" colSpan={1}> {i18n.t(label)} {deskStudy && `(${i18n.t('assessment.deskStudy')})`} </th> + {cellsExportAlways.map((cell) => { + const { columnName, format, unit, variableName } = cell + const { columnKey, value } = formatValue({ + assessmentName, + colName: String(columnName), + countryIso: countryIso as CountryIso, + cycleName, + data: results, + format, + tableName, + variableName, + }) + return ( + <td key={`${countryIso}${columnKey || columnName}`} className="fra-table__cell"> + <div className="number-input__readonly-view"> + {convertValue(value, unit ?? baseUnit, units[variableName])} + </div> + </td> + ) + })} {variables.map((variable) => columnsResults.map((column) => { - const { columnKey, value } = formatValue( + const { columnKey, value } = formatValue({ assessmentName, + colName: String(column), + countryIso: countryIso as CountryIso, cycleName, - String(column), - countryIso as CountryIso, - results, + data: results, tableName, - variable - ) + variableName: variable, + }) return ( <td key={`${countryIso}${columnKey || column}`} className="fra-table__cell"> diff --git a/src/client/pages/DataExport/ResultsTable/Title/Title.tsx b/src/client/pages/DataExport/ResultsTable/Title/Title.tsx index 211c6f818e..3563984ed6 100644 --- a/src/client/pages/DataExport/ResultsTable/Title/Title.tsx +++ b/src/client/pages/DataExport/ResultsTable/Title/Title.tsx @@ -4,8 +4,8 @@ import { useParams } from 'react-router-dom' import { Objects } from 'utils/objects' -import { Cols, Row } from 'meta/assessment' -import { Unit, UnitFactors } from 'meta/dataExport' +import { Cols, Row, Unit } from 'meta/assessment' +import { UnitFactors } from 'meta/dataExport' import { useCycle } from 'client/store/assessment' import { useTableSections } from 'client/store/metadata' diff --git a/src/client/pages/DataExport/ResultsTable/useFetchResults.ts b/src/client/pages/DataExport/ResultsTable/useFetchResults.ts index fda55181b7..9e301dde13 100644 --- a/src/client/pages/DataExport/ResultsTable/useFetchResults.ts +++ b/src/client/pages/DataExport/ResultsTable/useFetchResults.ts @@ -2,17 +2,19 @@ import { useEffect } from 'react' import { ApiEndPoint } from 'meta/api/endpoint' import { AssessmentName } from 'meta/assessment' +import { TableCell } from 'meta/assessment/table' import { RecordAssessmentData } from 'meta/data' import { useDataExportSelection } from 'client/store/ui/dataExport' import { useCountryIso, useGetRequest } from 'client/hooks' type Props = { - columnsAlwaysExport: Array<string> - tableName: string - sectionName: string assessmentName: AssessmentName + cellsExportAlways: Array<TableCell> + columnsAlwaysExport: Array<string> cycleName: string + sectionName: string + tableName: string } type UseFetchResults = { @@ -21,10 +23,13 @@ type UseFetchResults = { } export const useFetchResults = (props: Props): UseFetchResults => { - const { columnsAlwaysExport, tableName, sectionName, assessmentName, cycleName } = props + const { assessmentName, cellsExportAlways, columnsAlwaysExport, cycleName, sectionName, tableName } = props const selection = useDataExportSelection(sectionName) const countryIso = useCountryIso() + const cellsExportAlwaysColumns = cellsExportAlways.map((cell) => cell.columnName) + const cellsExportAlwaysVariables = cellsExportAlways.map((cell) => cell.variableName) + const { data: results = {}, dispatch: fetchResults, @@ -36,11 +41,10 @@ export const useFetchResults = (props: Props): UseFetchResults => { cycleName, tableNames: [tableName], countryISOs: selection.countryISOs, - variables: selection.sections[sectionName].variables, - columns: [...columnsAlwaysExport, ...selection.sections[sectionName].columns], + variables: [...cellsExportAlwaysVariables, ...selection.sections[sectionName].variables], + columns: [...cellsExportAlwaysColumns, ...columnsAlwaysExport, ...selection.sections[sectionName].columns], }, }) - // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(fetchResults, [selection]) diff --git a/src/client/pages/DataExport/utils/format.ts b/src/client/pages/DataExport/utils/format.ts index 42d5cf933e..75c25f881e 100644 --- a/src/client/pages/DataExport/utils/format.ts +++ b/src/client/pages/DataExport/utils/format.ts @@ -1,9 +1,9 @@ import { Numbers } from 'utils/numbers' import { CountryIso } from 'meta/area' -import { AssessmentName, AssessmentNames, CycleName } from 'meta/assessment' +import { AssessmentName, AssessmentNames, CycleName, TableCellNumberFormat, Unit } from 'meta/assessment' import { RecordAssessmentDatas, RecordCountryData } from 'meta/data' -import { Unit, UnitConverter, UnitFactors } from 'meta/dataExport' +import { UnitConverter, UnitFactors } from 'meta/dataExport' // import { getPanEuropeanTableMapping } from 'client/pages/DataExport/utils/panEuropean' @@ -22,15 +22,30 @@ const sections: Record<string, string> = { * @param {string} variableName - url params: current variable * @returns {{columnKey: string, value: string}} - formatted column and value, from results */ -export const formatValue = ( - assessmentName: AssessmentName, - cycleName: CycleName, - colName: string, - countryIso: CountryIso, - data: RecordCountryData, - tableName: string, +type FormatValueProps = { + assessmentName: AssessmentName + colName: string + countryIso: CountryIso + cycleName: CycleName + data: RecordCountryData + tableName: string variableName: string -): { columnKey: string; value: string } => { + format?: TableCellNumberFormat +} + +type Returned = { columnKey: string; value: string } + +export const formatValue = (props: FormatValueProps): Returned => { + const { + assessmentName, + colName, + countryIso, + cycleName, + data, + format = TableCellNumberFormat.decimal, + tableName, + variableName, + } = props const columnKey = colName let value = RecordAssessmentDatas.getDatum({ @@ -43,8 +58,18 @@ export const formatValue = ( variableName, }) + if (format === TableCellNumberFormat.original) { + return { columnKey, value } + } + // Convert value to string and check if it's a number - if (value && !Number.isNaN(+value)) value = Numbers.format(Number(value)) + if (value && !Number.isNaN(+value)) { + const numericValue = Number(value) + let precision + if (format === TableCellNumberFormat.integer) precision = 0 + value = Numbers.format(numericValue, precision) + } + if (value === 'NaN') value = '' return { columnKey, value } diff --git a/src/client/pages/DataExport/utils/labelKeys.ts b/src/client/pages/DataExport/utils/labelKeys.ts index 9c74378a40..e8a2e9d58e 100644 --- a/src/client/pages/DataExport/utils/labelKeys.ts +++ b/src/client/pages/DataExport/utils/labelKeys.ts @@ -1,5 +1,4 @@ -import { AssessmentName, AssessmentNames } from 'meta/assessment' -import { Unit } from 'meta/dataExport' +import { AssessmentName, AssessmentNames, Unit } from 'meta/assessment' import { isYearWithWord } from './checks' @@ -15,11 +14,12 @@ const unitLabelKeys: Record<string, string> = { const columnLabelKeys: Record<string, string> = { common_name: 'commonName', - scientific_name: 'scientificName', + growingStockMillionCubicMeter: 'millionCubicMeter', + growingStockPercent: 'growingStockPercent', + mostRecentYear: 'mostRecentYear', national_yes_no: 'national', + scientific_name: 'scientificName', sub_national_yes_no: 'subnational', - growingStockPercent: 'growingStockPercent', - growingStockMillionCubicMeter: 'millionCubicMeter', } export const getUnitLabelKey = (unit: string): string => (unitLabelKeys[unit] ? `unit.${unitLabelKeys[unit]}` : unit) diff --git a/src/i18n/resources/en.js b/src/i18n/resources/en.js index 9025b9c417..5c8d46b795 100644 --- a/src/i18n/resources/en.js +++ b/src/i18n/resources/en.js @@ -654,6 +654,7 @@ The FRA team fra@fao.org totalGrowingStock: 'Total growing stock', rankingYear: 'Ranking year 2015', growingStockPercent: '$t(unit.growingStockPercent)', + mostRecentYear: '$t(fra.growingStockComposition.mostRecentYear)', }, biomassStock: { diff --git a/src/meta/assessment/index.ts b/src/meta/assessment/index.ts index fa4e75b7b5..65a560fdb7 100644 --- a/src/meta/assessment/index.ts +++ b/src/meta/assessment/index.ts @@ -68,8 +68,8 @@ export { SectionNames } from './section' export { Sections } from './sections' export type { Settings } from './settings' export { SubSections } from './subSections' -export type { Table, TableColumnNames, TableName, TableProps } from './table' -export { TableNames } from './table' +export type { Table, TableCell, TableCellNames, TableColumnNames, TableName, TableProps } from './table' +export { TableCellNumberFormat, TableNames } from './table' export { Tables } from './tables' export type { TableSection, TableSectionProps } from './tableSection' export { TableSections } from './tableSections' diff --git a/src/meta/assessment/row.ts b/src/meta/assessment/row.ts index c040edb7bb..89dddc2584 100644 --- a/src/meta/assessment/row.ts +++ b/src/meta/assessment/row.ts @@ -43,6 +43,7 @@ export interface RowProps { * even if forestCharacteristics.naturalForestArea is included in its calculation formula. */ dependantsExclude?: Array<VariableCache> + excludeFromDataExport?: Record<CycleUuid, boolean> format?: { integer?: boolean } diff --git a/src/meta/assessment/rows.ts b/src/meta/assessment/rows.ts index 26170dfb9d..fc9a55896c 100644 --- a/src/meta/assessment/rows.ts +++ b/src/meta/assessment/rows.ts @@ -13,6 +13,8 @@ const cloneProps = (props: { cycleSource: Cycle; cycleTarget: Cycle; row: Row }) if (_props.calculateFn?.[cycleSourceUuid]) _props.calculateFn[cycleTargetUuid] = _props.calculateFn[cycleSourceUuid] if (_props.calculateIf?.[cycleSourceUuid]) _props.calculateIf[cycleTargetUuid] = _props.calculateIf[cycleSourceUuid] if (_props.chart?.[cycleSourceUuid]) _props.chart[cycleTargetUuid] = _props.chart[cycleSourceUuid] + if (_props.excludeFromDataExport?.[cycleSourceUuid]) + _props.excludeFromDataExport[cycleTargetUuid] = _props.excludeFromDataExport[cycleSourceUuid] if (_props.linkToSection?.[cycleSourceUuid]) _props.linkToSection[cycleTargetUuid] = _props.linkToSection[cycleSourceUuid] if (_props.validateFns?.[cycleSourceUuid]) _props.validateFns[cycleTargetUuid] = _props.validateFns[cycleSourceUuid] diff --git a/src/meta/assessment/table.ts b/src/meta/assessment/table.ts index 0f9c513765..93fa10697f 100644 --- a/src/meta/assessment/table.ts +++ b/src/meta/assessment/table.ts @@ -27,12 +27,28 @@ export enum TableNames { originalDataPointValue = 'originalDataPointValue', } +export enum TableCellNumberFormat { + decimal = 'decimal', + integer = 'integer', + original = 'original', +} + +export type TableCell = { + columnName: ColName + variableName: VariableName + format?: TableCellNumberFormat + unit?: Unit | null +} + +export type TableCellNames = Record<CycleUuid, Array<TableCell>> + // array of column names indexed by cycle uuid export type TableColumnNames = Record<CycleUuid, Array<ColName>> export type TableName = string export interface TableProps { + cellsExportAlways?: TableCellNames columnNames: TableColumnNames columnsExport?: TableColumnNames columnsExportAlways?: TableColumnNames diff --git a/src/meta/assessment/tables.ts b/src/meta/assessment/tables.ts index 64bc356d53..0f969849ac 100644 --- a/src/meta/assessment/tables.ts +++ b/src/meta/assessment/tables.ts @@ -10,6 +10,8 @@ const cloneProps = (props: { cycleSource: Cycle; cycleTarget: Cycle; table: Tabl const _props: Table['props'] = { ...table.props } _props.cycles.push(cycleTargetUuid) + if (_props.cellsExportAlways?.[cycleSourceUuid]) + _props.cellsExportAlways[cycleTargetUuid] = _props.cellsExportAlways[cycleSourceUuid] if (_props.columnNames?.[cycleSourceUuid]) _props.columnNames[cycleTargetUuid] = _props.columnNames[cycleSourceUuid] if (_props.columnsExport?.[cycleSourceUuid]) _props.columnsExport[cycleTargetUuid] = _props.columnsExport[cycleSourceUuid] diff --git a/src/meta/dataExport/index.ts b/src/meta/dataExport/index.ts index 5edc1dbad3..8de5a9353f 100644 --- a/src/meta/dataExport/index.ts +++ b/src/meta/dataExport/index.ts @@ -1,2 +1,2 @@ export type { UnitFactor } from './unit' -export { Unit, UnitConverter, UnitFactors } from './unit' +export { UnitConverter, UnitFactors } from './unit' diff --git a/src/meta/dataExport/unit.ts b/src/meta/dataExport/unit.ts index 4cd58eb483..54efe58abd 100644 --- a/src/meta/dataExport/unit.ts +++ b/src/meta/dataExport/unit.ts @@ -1,24 +1,5 @@ import { Numbers } from 'utils/numbers' -export enum Unit { - haThousand = 'haThousand', - haThousandPerYear = 'haThousandPerYear', - tonnesPerHa = 'tonnesPerHa', - cubicMeterPerHa = 'cubicMeterPerHa', - millionTonnes = 'millionTonnes', - millionsCubicMeterOverBark = 'millionsCubicMeterOverBark', - thousandCubicMeter = 'thousandCubicMeter', - thousandCubicMeterOverBark = 'thousandCubicMeterOverBark', - thousandCubicMeterRWE = 'thousandCubicMeterRWE', - thousandPersons = 'thousandPersons', - fte1000 = 'fte1000', - numberOfStudents = 'numberOfStudents', - absoluteNumber = 'absoluteNumber', - annualNumberOfVisitsMillion = 'annualNumberOfVisitsMillion', - millionNationalCurrency = 'millionNationalCurrency', - facilityLengthIn1000Km = 'facilityLengthIn1000Km', -} - export interface UnitFactor extends Record<string, number> { haThousand: number ha: number diff --git a/src/server/repository/adapter/row.ts b/src/server/repository/adapter/row.ts index 07c697c735..f6a18409c0 100644 --- a/src/server/repository/adapter/row.ts +++ b/src/server/repository/adapter/row.ts @@ -14,7 +14,7 @@ export interface RowDB { export const RowAdapter = (rowDB: RowDB): Row => { const { - props: { calculateFn, calculateIf, chart, linkToSection, validateFns, withReview, ...rest }, + props: { calculateFn, calculateIf, chart, excludeFromDataExport, linkToSection, validateFns, withReview, ...rest }, ...row } = rowDB @@ -25,6 +25,7 @@ export const RowAdapter = (rowDB: RowDB): Row => { calculateFn, calculateIf, chart, + excludeFromDataExport, linkToSection, validateFns, withReview, diff --git a/src/test/migrations/steps/20241003084429-step-add-cellsExportAlways-to-growingStockComposition2025-and-excludeFromDataExport-to-row.ts b/src/test/migrations/steps/20241003084429-step-add-cellsExportAlways-to-growingStockComposition2025-and-excludeFromDataExport-to-row.ts new file mode 100644 index 0000000000..884da3e89d --- /dev/null +++ b/src/test/migrations/steps/20241003084429-step-add-cellsExportAlways-to-growingStockComposition2025-and-excludeFromDataExport-to-row.ts @@ -0,0 +1,57 @@ +import { AssessmentNames } from 'meta/assessment' + +import { AssessmentController } from 'server/controller/assessment' +import { BaseProtocol, Schemas } from 'server/db' + +export default async (client: BaseProtocol) => { + const { assessment, cycle } = await AssessmentController.getOneWithCycle( + { assessmentName: AssessmentNames.fra, cycleName: '2025' }, + client + ) + + const schemaAssessment = Schemas.getName(assessment) + + await client.query(` + update ${schemaAssessment}."table" t + set props = props + || jsonb_build_object( + 'cellsExportAlways', + jsonb_build_object( + '${cycle.uuid}', + '[{ + "columnName": "mostRecentYear", + "variableName": "mostRecentYear", + "unit": null, + "format": "original" + }]'::jsonb + ) + ) + from ( + select t2.id + from ${schemaAssessment}."table" t2 + left join ${schemaAssessment}.table_section ts on t2.table_section_id = ts.id + left join ${schemaAssessment}.section s on s.id = ts.section_id + where s.props ->> 'name' = 'growingStockComposition' + and t2.props ->> 'name' = 'growingStockComposition2025' + ) as src + where t.id = src.id; + `) + + await client.query(` + update ${schemaAssessment}."row" r + set props = props || jsonb_build_object('excludeFromDataExport', jsonb_build_object('${cycle.uuid}', true)) + where r.props ->> 'type' = 'data' + and r.props ->> 'variableName' = 'mostRecentYear' + and r.table_id = ( + select t.id + from ${schemaAssessment}."table" t + left join ${schemaAssessment}.table_section ts on t.table_section_id = ts.id + left join ${schemaAssessment}.section s on s.id = ts.section_id + where s.props ->> 'name' = 'growingStockComposition' + and t.props ->> 'name' = 'growingStockComposition2025' + ); + `) + + await AssessmentController.generateMetaCache(client) + await AssessmentController.generateMetadataCache({ assessment }, client) +}