diff --git a/antarest/__init__.py b/antarest/__init__.py index 8fd62ea94a..adaa163763 100644 --- a/antarest/__init__.py +++ b/antarest/__init__.py @@ -19,9 +19,9 @@ # Standard project metadata -__version__ = "2.18.2" +__version__ = "2.18.3" __author__ = "RTE, Antares Web Team" -__date__ = "2024-12-11" +__date__ = "2024-12-17" # noinspection SpellCheckingInspection __credits__ = "(c) Réseau de Transport de l’Électricité (RTE)" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a8c6b3cf13..959376e0d5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,18 @@ Antares Web Changelog ===================== +v2.18.3 (2024-12-17) +-------------------- + +## What's Changed + +### Bug Fixes + +* **ui-results**: resolve data misalignment in matrix column filtering [`2269`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/2269) + +**Full Changelog**: https://github.com/AntaresSimulatorTeam/AntaREST/compare/v2.18.2...v2.18.3 + + v2.18.2 (2024-12-11) -------------------- diff --git a/pyproject.toml b/pyproject.toml index bc4332d38f..6073d80770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools"] [project] name = "AntaREST" -version = "2.18.2" +version = "2.18.3" authors = [{name="RTE, Antares Web Team", email="andrea.sgattoni@rte-france.com" }] description="Antares Server" readme = {file = "README.md", content-type = "text/markdown"} diff --git a/sonar-project.properties b/sonar-project.properties index 704e6924d8..8c55e49a3b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,5 +6,5 @@ sonar.exclusions=antarest/gui.py,antarest/main.py sonar.python.coverage.reportPaths=coverage.xml sonar.python.version=3.11 sonar.javascript.lcov.reportPaths=webapp/coverage/lcov.info -sonar.projectVersion=2.18.2 +sonar.projectVersion=2.18.3 sonar.coverage.exclusions=antarest/gui.py,antarest/main.py,antarest/singleton_services.py,antarest/worker/archive_worker_service.py,webapp/**/*,,antarest/fastapi_jwt_auth/** \ No newline at end of file diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 19a7d280fb..7fcf0b0270 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "antares-web", - "version": "2.18.2", + "version": "2.18.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "antares-web", - "version": "2.18.2", + "version": "2.18.3", "dependencies": { "@emotion/react": "11.13.3", "@emotion/styled": "11.13.0", diff --git a/webapp/package.json b/webapp/package.json index a4efb1d608..aea940a029 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "antares-web", - "version": "2.18.2", + "version": "2.18.3", "private": true, "type": "module", "scripts": { diff --git a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx index fac74da93c..5e460b5d3e 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx @@ -62,7 +62,7 @@ interface Props { studyId: string; path: string; colHeaders: string[][]; - onColHeadersChange: (colHeaders: string[][]) => void; + onColHeadersChange: (colHeaders: string[][], indices: number[]) => void; } function ResultFilters({ @@ -104,48 +104,61 @@ function ResultFilters({ ); }, [colHeaders]); - useEffect(() => { - const filteredHeaders = parsedHeaders.filter((header) => { - // Apply search filters - if (filters.search) { - const matchesVariable = matchesSearchTerm( - header.variable, - filters.search, - ); + // Process headers while keeping track of their original positions + // This ensures we can properly filter the data matrix later + // Example: if we filter out column 1, we need to know that column 2 + // becomes column 1 in the filtered view but maps to index 2 in the data + const filteredHeaders = useMemo(() => { + return parsedHeaders + .map((header, index) => ({ ...header, index })) + .filter((header) => { + // Apply search filter + if (filters.search) { + const matchesVariable = matchesSearchTerm( + header.variable, + filters.search, + ); - const matchesUnit = matchesSearchTerm(header.unit, filters.search); + const matchesUnit = matchesSearchTerm(header.unit, filters.search); - if (!matchesVariable && !matchesUnit) { - return false; + if (!matchesVariable && !matchesUnit) { + return false; + } } - } - // Apply stat filters - if (header.stat) { - const stat = header.stat.toLowerCase(); + // Apply statistical filters + if (header.stat) { + const stat = header.stat.toLowerCase(); - if (!filters.exp && stat.includes("exp")) { - return false; - } - if (!filters.min && stat.includes("min")) { - return false; - } - if (!filters.max && stat.includes("max")) { - return false; - } - if (!filters.std && stat.includes("std")) { - return false; + if (!filters.exp && stat.includes("exp")) { + return false; + } + if (!filters.min && stat.includes("min")) { + return false; + } + if (!filters.max && stat.includes("max")) { + return false; + } + if (!filters.std && stat.includes("std")) { + return false; + } + if (!filters.values && stat.includes("values")) { + return false; + } } - if (!filters.values && stat.includes("values")) { - return false; - } - } - return true; - }); + return true; + }); + }, [filters, parsedHeaders]); - onColHeadersChange(filteredHeaders.map((h) => h.original)); - }, [filters, parsedHeaders, onColHeadersChange]); + // Notify parent of both filtered headers and their original indices + // This allows the parent to correctly map the filtered view back to the original data + useEffect(() => { + onColHeadersChange( + filteredHeaders.map((h) => h.original), + filteredHeaders.map((h) => h.index), + ); + }, [filteredHeaders, onColHeadersChange]); //////////////////////////////////////////////////////////////// // Event handlers diff --git a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx index 3aaaf4509b..1814bd876d 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx @@ -68,6 +68,8 @@ import { getStudyMatrixIndex } from "../../../../../../services/api/matrix.ts"; import { MatrixGridSynthesis } from "@/components/common/Matrix/components/MatrixGridSynthesis"; import { ResultMatrixDTO } from "@/components/common/Matrix/shared/types.ts"; +type SetResultColHeaders = (headers: string[][], indices: number[]) => void; + function ResultDetails() { const { study } = useOutletContext<{ study: StudyMetadata }>(); const { outputId } = useParams(); @@ -84,7 +86,11 @@ function ResultDetails() { const [itemType, setItemType] = useState(OutputItemType.Areas); const [selectedItemId, setSelectedItemId] = useState(""); const [searchValue, setSearchValue] = useState(""); + // Store filtered headers and their original indices separately + // This allows us to correctly map the data rows to their corresponding headers + // when some columns are filtered out const [resultColHeaders, setResultColHeaders] = useState([]); + const [headerIndices, setHeaderIndices] = useState([]); const isSynthesis = itemType === OutputItemType.Synthesis; const { t } = useTranslation(); const navigate = useNavigate(); @@ -143,16 +149,24 @@ function ResultDetails() { } const res = await getStudyData(study.id, path); - // TODO add backend parse + if (typeof res === "string") { const fixed = res .replace(/NaN/g, '"NaN"') .replace(/Infinity/g, '"Infinity"'); - return JSON.parse(fixed); + const parsed = JSON.parse(fixed); + + return { + ...parsed, + indices: Array.from({ length: parsed.columns.length }, (_, i) => i), + }; } - return res; + return { + ...res, + indices: Array.from({ length: res.columns.length }, (_, i) => i), + }; }, { resetDataOnReload: true, @@ -161,6 +175,19 @@ function ResultDetails() { }, ); + // Transform the matrix data by keeping only the columns that match our filters + // headerIndices contains the original positions of our kept columns, ensuring + // the data stays aligned with its corresponding headers + const filteredData = useMemo(() => { + if (!matrixRes.data) { + return []; + } + + return matrixRes.data.data.map((row) => { + return headerIndices.map((index) => row[index]); + }); + }, [matrixRes.data, headerIndices]); + const synthesisRes = usePromise( () => { if (outputId && selectedItem && isSynthesis) { @@ -213,6 +240,11 @@ function ResultDetails() { } }; + const handleColHeadersChange: SetResultColHeaders = (headers, indices) => { + setResultColHeaders(headers); + setHeaderIndices(indices); + }; + //////////////////////////////////////////////////////////////// // JSX //////////////////////////////////////////////////////////////// @@ -295,7 +327,7 @@ function ResultDetails() { studyId={study.id} path={path} colHeaders={matrixRes.data?.columns || []} - onColHeadersChange={setResultColHeaders} + onColHeadersChange={handleColHeadersChange} />