diff --git a/antarest/__init__.py b/antarest/__init__.py index 9e6608e87c..c3d6be21fb 100644 --- a/antarest/__init__.py +++ b/antarest/__init__.py @@ -7,9 +7,9 @@ # Standard project metadata -__version__ = "2.16.4" +__version__ = "2.16.5" __author__ = "RTE, Antares Web Team" -__date__ = "2024-02-14" +__date__ = "2024-02-29" # noinspection SpellCheckingInspection __credits__ = "(c) Réseau de Transport de l’Électricité (RTE)" diff --git a/antarest/study/model.py b/antarest/study/model.py index 78cd72aa2e..ab58219c8d 100644 --- a/antarest/study/model.py +++ b/antarest/study/model.py @@ -208,8 +208,9 @@ class Study(Base): # type: ignore __mapper_args__ = {"polymorphic_identity": "study", "polymorphic_on": type} def __str__(self) -> str: + cls = self.__class__.__name__ return ( - f"[Study]" + f"[{cls}]" f" id={self.id}," f" type={self.type}," f" name={self.name}," diff --git a/antarest/study/storage/utils.py b/antarest/study/storage/utils.py index 365eb1f370..5dc97b081b 100644 --- a/antarest/study/storage/utils.py +++ b/antarest/study/storage/utils.py @@ -52,7 +52,7 @@ def update_antares_info(metadata: Study, study_tree: FileStudyTree, *, update_au study_data_info["antares"]["created"] = metadata.created_at.timestamp() study_data_info["antares"]["lastsave"] = metadata.updated_at.timestamp() study_data_info["antares"]["version"] = metadata.version - if update_author: + if update_author and metadata.additional_data: study_data_info["antares"]["author"] = metadata.additional_data.author study_tree.save(study_data_info, ["study"]) diff --git a/antarest/study/storage/variantstudy/model/dbmodel.py b/antarest/study/storage/variantstudy/model/dbmodel.py index f172591d68..f6874a5495 100644 --- a/antarest/study/storage/variantstudy/model/dbmodel.py +++ b/antarest/study/storage/variantstudy/model/dbmodel.py @@ -112,3 +112,7 @@ def is_snapshot_up_to_date(self) -> bool: and (self.snapshot.created_at >= self.updated_at) and (self.snapshot_dir / "study.antares").is_file() ) + + def has_snapshot(self) -> bool: + """Check if the snapshot exists.""" + return (self.snapshot is not None) and (self.snapshot_dir / "study.antares").is_file() diff --git a/antarest/study/storage/variantstudy/snapshot_generator.py b/antarest/study/storage/variantstudy/snapshot_generator.py index 50972ae99a..138089a35e 100644 --- a/antarest/study/storage/variantstudy/snapshot_generator.py +++ b/antarest/study/storage/variantstudy/snapshot_generator.py @@ -246,7 +246,7 @@ def search_ref_study( # To reuse the snapshot of the current variant, the last executed command # must be one of the commands of the current variant. curr_variant = descendants[-1] - if curr_variant.snapshot: + if curr_variant.has_snapshot(): last_exec_cmd = curr_variant.snapshot.last_executed_command command_ids = [c.id for c in curr_variant.commands] # If the variant has no command, we can reuse the snapshot if it is recent diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1399b80670..690de1a7eb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,25 @@ Antares Web Changelog ===================== +v2.16.5 (2024-02-29) +-------------------- + +### Features + +* **ui-results:** add results matrix timestamps [`#1945`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1945) + + +### Bug Fixes + +* **ui-hydro:** disable stretch to fix display issue on some matrices [`#1945`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1945) +* **ui-hydro:** add missing matrix path encoding [`#1940`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1940) +* **ui-results:** disable stretch to fix display issue [`#1945`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1945) +* **ui-results:** prevent duplicate updates on same toggle button click [`#1945`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1945) +* **ui-thermal:** update cluster group options to handle `Other 1` [`#1945`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1945) +* **variants:** correct the generation of variant when a snapshot is removed [`#1947`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1947) +* **study:** correct access to study `additional_data` [`#1949`](https://github.com/AntaresSimulatorTeam/AntaREST/pull/1949) + + v2.16.4 (2024-02-14) -------------------- diff --git a/setup.py b/setup.py index dc092ab8c7..1e0d76dfe5 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="AntaREST", - version="2.16.4", + version="2.16.5", description="Antares Server", long_description=Path("README.md").read_text(encoding="utf-8"), long_description_content_type="text/markdown", diff --git a/sonar-project.properties b/sonar-project.properties index 8f0a02dd21..beb967df7e 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.8 sonar.javascript.lcov.reportPaths=webapp/coverage/lcov.info -sonar.projectVersion=2.16.4 +sonar.projectVersion=2.16.5 sonar.coverage.exclusions=antarest/gui.py,antarest/main.py,antarest/singleton_services.py,antarest/worker/archive_worker_service.py,webapp/**/* \ No newline at end of file diff --git a/tests/study/storage/variantstudy/model/test_dbmodel.py b/tests/study/storage/variantstudy/model/test_dbmodel.py index 0109072303..6ed1bbcba1 100644 --- a/tests/study/storage/variantstudy/model/test_dbmodel.py +++ b/tests/study/storage/variantstudy/model/test_dbmodel.py @@ -188,7 +188,7 @@ def test_init__without_snapshot(self, db_session: Session, raw_study_id: str, us obj: VariantStudy = db_session.query(VariantStudy).filter(VariantStudy.id == variant_study_id).one() # check Study representation - assert str(obj).startswith(f"[Study] id={variant_study_id}") + assert str(obj).startswith(f"[VariantStudy] id={variant_study_id}") # check Study fields assert obj.id == variant_study_id diff --git a/webapp/package-lock.json b/webapp/package-lock.json index cfc4573790..8a6261057b 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "antares-web", - "version": "2.16.4", + "version": "2.16.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "antares-web", - "version": "2.16.4", + "version": "2.16.5", "dependencies": { "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", diff --git a/webapp/package.json b/webapp/package.json index 4699b94a7c..be76665a99 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "antares-web", - "version": "2.16.4", + "version": "2.16.5", "private": true, "type": "module", "scripts": { diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/HydroMatrix.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/HydroMatrix.tsx index 2a8796074c..12d8a773d3 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/HydroMatrix.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/HydroMatrix.tsx @@ -32,6 +32,7 @@ function HydroMatrix({ type }: Props) { fetchFn={hydroMatrix.fetchFn} disableEdit={hydroMatrix.disableEdit} enablePercentDisplay={hydroMatrix.enablePercentDisplay} + stretch={hydroMatrix.stretch} /> ); diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts index ed8457afe4..7c0a130f95 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts @@ -37,6 +37,7 @@ export interface HydroMatrixProps { fetchFn?: fetchMatrixFn; disableEdit?: boolean; enablePercentDisplay?: boolean; + stretch?: boolean; // TODO: Remove this once the `EditableMatrix` component is refactored } type Matrices = Record; @@ -125,6 +126,7 @@ export const MATRICES: Matrices = { "Pumping Max Energy (Hours at Pmax)", ], stats: MatrixStats.NOCOL, + stretch: false, }, [HydroMatrixType.ReservoirLevels]: { title: "Reservoir Levels", @@ -185,6 +187,7 @@ export const MATRICES: Matrices = { "December", ], stats: MatrixStats.NOCOL, + stretch: false, }, [HydroMatrixType.Allocation]: { title: "Allocation", diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Thermal/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Thermal/utils.ts index 68284b81f3..d113e06c4f 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Thermal/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Thermal/utils.ts @@ -16,7 +16,7 @@ export const THERMAL_GROUPS = [ "Mixed fuel", "Nuclear", "Oil", - "Other", + "Other 1", "Other 2", "Other 3", "Other 4", 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 a22ba12022..e61c2ac3d2 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx @@ -49,6 +49,7 @@ import ButtonBack from "../../../../../common/ButtonBack"; import BooleanFE from "../../../../../common/fieldEditors/BooleanFE"; import SelectFE from "../../../../../common/fieldEditors/SelectFE"; import NumberFE from "../../../../../common/fieldEditors/NumberFE"; +import moment from "moment"; function ResultDetails() { const { study } = useOutletContext<{ study: StudyMetadata }>(); @@ -148,15 +149,27 @@ function ResultDetails() { }, ); + // !NOTE: Workaround to display the date in the correct format, to be replaced by a proper solution. + const dateTimeFromIndex = useMemo(() => { + if (!matrixRes.data) return []; + + return matrixRes.data.index.map((dateTime) => { + const parsedDate = moment(dateTime, "MM/DD HH:mm"); + return parsedDate.format("ddd D MMM HH:mm"); + }); + }, [matrixRes.data]); + //////////////////////////////////////////////////////////////// // Event Handlers //////////////////////////////////////////////////////////////// const handleItemTypeChange: ToggleButtonGroupProps["onChange"] = ( _, - value: OutputItemType, + newValue: OutputItemType, ) => { - setItemType(value); + if (newValue && newValue !== itemType) { + setItemType(newValue); + } }; const handleDownload = (matrixData: MatrixType, fileName: string): void => { @@ -366,6 +379,8 @@ function ResultDetails() { ) diff --git a/webapp/src/components/common/EditableMatrix/index.tsx b/webapp/src/components/common/EditableMatrix/index.tsx index 0d1c9e21b4..87aa8441f2 100644 --- a/webapp/src/components/common/EditableMatrix/index.tsx +++ b/webapp/src/components/common/EditableMatrix/index.tsx @@ -28,6 +28,7 @@ interface PropTypes { rowNames?: string[]; computStats?: MatrixStats; isPercentDisplayEnabled?: boolean; + stretch?: boolean; } type CellType = Array; @@ -55,6 +56,7 @@ function EditableMatrix(props: PropTypes) { rowNames, computStats, isPercentDisplayEnabled = false, + stretch = true, } = props; const { data = [], columns = [], index = [] } = matrix; const prependIndex = index.length > 0 && matrixTime; @@ -176,7 +178,7 @@ function EditableMatrix(props: PropTypes) { data={grid} width="100%" height="100%" - stretchH="all" + stretchH={stretch ? "all" : "none"} className="editableMatrix" colHeaders rowHeaderWidth={matrixRowNames ? 150 : undefined} @@ -186,7 +188,6 @@ function EditableMatrix(props: PropTypes) { beforeKeyDown={(e) => handleKeyDown(e)} columns={formattedColumns} rowHeaders={matrixRowNames || true} - manualColumnResize /> ); diff --git a/webapp/src/components/common/EditableMatrix/style.ts b/webapp/src/components/common/EditableMatrix/style.ts index da7da1910b..dd62aecfd2 100644 --- a/webapp/src/components/common/EditableMatrix/style.ts +++ b/webapp/src/components/common/EditableMatrix/style.ts @@ -6,7 +6,7 @@ export const Root = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", alignItems: "center", - overflow: "auto", + overflow: "hidden", })); export const StyledButton = styled(Button)(({ theme }) => ({ diff --git a/webapp/src/components/common/MatrixInput/index.tsx b/webapp/src/components/common/MatrixInput/index.tsx index 0d43522296..7807cd94c4 100644 --- a/webapp/src/components/common/MatrixInput/index.tsx +++ b/webapp/src/components/common/MatrixInput/index.tsx @@ -38,6 +38,7 @@ interface Props { fetchFn?: fetchMatrixFn; disableEdit?: boolean; enablePercentDisplay?: boolean; + stretch?: boolean; } function MatrixInput({ @@ -50,6 +51,7 @@ function MatrixInput({ fetchFn, disableEdit, enablePercentDisplay, + stretch, }: Props) { const { enqueueSnackbar } = useSnackbar(); const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); @@ -211,6 +213,7 @@ function MatrixInput({ onUpdate={handleUpdate} computStats={computStats} isPercentDisplayEnabled={enablePercentDisplay} + stretch={stretch} /> ) : ( !isLoading && ( diff --git a/webapp/src/components/common/fieldEditors/ListFE/index.tsx b/webapp/src/components/common/fieldEditors/ListFE/index.tsx index 5d3048b2ec..d72fa71c81 100644 --- a/webapp/src/components/common/fieldEditors/ListFE/index.tsx +++ b/webapp/src/components/common/fieldEditors/ListFE/index.tsx @@ -34,9 +34,10 @@ import reactHookFormSupport, { import { createFakeBlurEventHandler, createFakeChangeEventHandler, + createFakeInputElement, FakeBlurEventHandler, FakeChangeEventHandler, - FakeHTMLInputElement, + InputObject, } from "../../../../utils/feUtils"; import { makeLabel, makeListItems } from "./utils"; @@ -97,7 +98,7 @@ function ListFE(props: ListFEProps) { // Trigger event handlers useUpdateEffect(() => { if (onChange || onBlur) { - const fakeInputElement: FakeHTMLInputElement = { + const fakeInputElement: InputObject = { value: listItems.map((item) => item.value), name, }; @@ -108,10 +109,10 @@ function ListFE(props: ListFEProps) { // Set ref useEffect(() => { - const fakeInputElement: FakeHTMLInputElement = { + const fakeInputElement = createFakeInputElement({ value: listItems.map((item) => item.value), name, - }; + }); setRef(inputRef, fakeInputElement); }, [inputRef, listItems, name]); diff --git a/webapp/src/services/api/matrix.ts b/webapp/src/services/api/matrix.ts index 9b2d4f5213..1eff05f7c9 100644 --- a/webapp/src/services/api/matrix.ts +++ b/webapp/src/services/api/matrix.ts @@ -97,7 +97,7 @@ export const editMatrix = async ( matrixEdit: MatrixEditDTO[], ): Promise => { const res = await client.put( - `/v1/studies/${sid}/matrix?path=${path}`, + `/v1/studies/${sid}/matrix?path=${encodeURIComponent(path)}`, matrixEdit, ); return res.data; diff --git a/webapp/src/services/api/studies/tableMode/index.ts b/webapp/src/services/api/studies/tableMode/index.ts index cd77a71891..c6c4db5b80 100644 --- a/webapp/src/services/api/studies/tableMode/index.ts +++ b/webapp/src/services/api/studies/tableMode/index.ts @@ -1,10 +1,10 @@ -import { snakeCase } from "lodash"; import { DeepPartial } from "react-hook-form"; import { StudyMetadata } from "../../../../common/types"; import client from "../../client"; import { format } from "../../../../utils/stringUtils"; import { TABLE_MODE_API_URL } from "../../constants"; import type { TableData, TableModeColumnsForType, TableModeType } from "./type"; +import { toColumnApiName } from "./utils"; export async function getTableMode( studyId: StudyMetadata["id"], @@ -15,7 +15,7 @@ export async function getTableMode( const res = await client.get(url, { params: { table_type: type, - columns: columns.map(snakeCase).join(","), + columns: columns.map(toColumnApiName).join(","), }, }); return res.data; diff --git a/webapp/src/services/api/studies/tableMode/utils.ts b/webapp/src/services/api/studies/tableMode/utils.ts new file mode 100644 index 0000000000..e856785502 --- /dev/null +++ b/webapp/src/services/api/studies/tableMode/utils.ts @@ -0,0 +1,11 @@ +import { snakeCase } from "lodash"; +import { TableModeColumnsForType, TableModeType } from "./type"; + +export function toColumnApiName( + column: TableModeColumnsForType[number], +) { + if (column === "co2") { + return "co2"; + } + return snakeCase(column); +} diff --git a/webapp/src/utils/feUtils.ts b/webapp/src/utils/feUtils.ts index 8c3bd9f25b..636fcb82ea 100644 --- a/webapp/src/utils/feUtils.ts +++ b/webapp/src/utils/feUtils.ts @@ -1,13 +1,13 @@ import { HTMLInputTypeAttribute } from "react"; -export interface FakeHTMLInputElement { +export interface InputObject { value: unknown; checked?: boolean; type?: HTMLInputTypeAttribute; name?: string; } -type Target = HTMLInputElement | FakeHTMLInputElement; +type Target = HTMLInputElement | InputObject; export interface FakeChangeEventHandler { target: Target; @@ -36,3 +36,11 @@ export function createFakeBlurEventHandler( type: "blur", }; } + +export function createFakeInputElement(obj: InputObject): HTMLInputElement { + const inputElement = document.createElement("input"); + inputElement.name = obj.name || ""; + inputElement.value = obj.value as string; + + return inputElement; +}