Quellcode durchsuchen

Merge branch 'develop' into feature/#398-expand-exposed-type-parameter

Jean-Robin vor 5 Monaten
Ursprung
Commit
aa9a18a6a9

+ 3 - 0
.github/actions/gui-test/e2e/action.yml

@@ -2,6 +2,9 @@ name: end-to-end test
 runs:
   using: "composite"
   steps:
+    - name: Check Python and iPython versions
+      shell: bash
+      run: pipenv run python --version && pipenv run ipython --version
     - name: install kernel
       shell: bash
       run: pipenv run ipython kernel install --name "python3" --user

+ 1 - 1
frontend/taipy-gui/src/components/Taipy/Chat.spec.tsx

@@ -212,7 +212,7 @@ describe("Chat Component", () => {
         });
         jest.restoreAllMocks();
     });
-    it("Not upload image over a file size limit", async () => {
+    it("does not upload image over a file size limit", async () => {
         const dispatch = jest.fn();
         const state: TaipyState = INITIAL_STATE;
         const { getByText, getByAltText } = render(

+ 10 - 3
frontend/taipy-gui/src/components/Taipy/Chat.tsx

@@ -22,6 +22,7 @@ import React, {
     ReactNode,
     lazy,
     ChangeEvent,
+    UIEvent,
 } from "react";
 import { SxProps, Theme, darken, lighten } from "@mui/material/styles";
 import Avatar from "@mui/material/Avatar";
@@ -250,6 +251,7 @@ const Chat = (props: ChatProps) => {
     const [imagePreview, setImagePreview] = useState<string | null>(null);
     const [objectURLs, setObjectURLs] = useState<string[]>([]);
     const fileInputRef = useRef<HTMLInputElement>(null);
+    const userScrolled = useRef(false);
 
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
     const active = useDynamicProperty(props.active, props.defaultActive, true);
@@ -424,7 +426,7 @@ const Chat = (props: ChatProps) => {
     );
 
     const showBottom = useCallback(() => {
-        anchorDivRef.current?.scrollIntoView();
+        anchorDivRef.current?.scrollIntoView && anchorDivRef.current?.scrollIntoView();
         setShowMessage(false);
     }, []);
 
@@ -450,8 +452,9 @@ const Chat = (props: ChatProps) => {
                 }
             }
             page.current.key = getChatKey(0, pageSize);
+            !userScrolled.current && showBottom();
         }
-    }, [refresh, pageSize, props.messages]);
+    }, [refresh, pageSize, props.messages, showBottom]);
 
     useEffect(() => {
         if (showMessage && !isAnchorDivVisible) {
@@ -490,10 +493,14 @@ const Chat = (props: ChatProps) => {
         [loadMoreItems]
     );
 
+    const handleOnScroll = useCallback((evt: UIEvent) => {
+        userScrolled.current = (evt.target as HTMLDivElement).scrollHeight - (evt.target as HTMLDivElement).offsetHeight - (evt.target as HTMLDivElement).scrollTop > 1;
+    }, []);
+
     return (
         <Tooltip title={hover || ""}>
             <Paper className={`${className} ${getComponentClassName(props.children)}`} sx={boxSx} id={id}>
-                <Grid container rowSpacing={2} sx={gridSx} ref={scrollDivRef}>
+                <Grid container rowSpacing={2} sx={gridSx} ref={scrollDivRef} onScroll={handleOnScroll}>
                     {rows.length && !rows[0] ? (
                         <Grid className={getSuffixedClassNames(className, "-load")} size={12} sx={noAnchorSx}>
                             <Box sx={loadMoreSx}>

+ 136 - 10
frontend/taipy-gui/src/components/Taipy/Metric.spec.tsx

@@ -239,7 +239,7 @@ describe("Metric Component", () => {
     });
 
     it("applies style correctly when deltaColor is set", async () => {
-        const { container } = render(<Metric delta={10} deltaColor="#FF4136"  />);
+        const { container } = render(<Metric delta={10} deltaColor="#FF4136" />);
         await waitFor(() => {
             const elt = container.querySelector(".delta");
             expect(elt).toHaveStyle({
@@ -249,7 +249,7 @@ describe("Metric Component", () => {
     });
 
     it("applies style correctly when deltaColor is set invert", async () => {
-        const { container } = render(<Metric delta={10} deltaColor="invert"  />);
+        const { container } = render(<Metric delta={10} deltaColor="invert" />);
         await waitFor(() => {
             const elt = container.querySelector(".delta");
             expect(elt).toHaveStyle({
@@ -259,7 +259,7 @@ describe("Metric Component", () => {
     });
 
     it("processes type and threshold props correctly when type is linear", async () => {
-        const { container } = render(<Metric type="linear" threshold={50}  />);
+        const { container } = render(<Metric type="linear" threshold={50} />);
         await waitFor(() => {
             const elt = container.querySelector(".bullet");
             expect(elt).toBeInTheDocument();
@@ -267,7 +267,7 @@ describe("Metric Component", () => {
     });
 
     it("processes type and threshold props correctly when type is not linear", async () => {
-        const { container } = render(<Metric type="angular" threshold={50}  />);
+        const { container } = render(<Metric type="angular" threshold={50} />);
         await waitFor(() => {
             const elt = container.querySelector(".angular");
             expect(elt).toBeInTheDocument();
@@ -286,6 +286,132 @@ describe("Metric Component", () => {
         });
     });
 
+    it("applies style correctly when height is set to 100px", async () => {
+        const { container } = render(<Metric height="100px" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100%",
+                height: "100px",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30em", async () => {
+        const { container } = render(<Metric height="30em" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100%",
+                height: "30em",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30%", async () => {
+        const { container } = render(<Metric height="30%" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100%",
+                height: "30%",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30vh", async () => {
+        const { container } = render(<Metric height="30vh" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100%",
+                height: "30vh",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30vw", async () => {
+        const { container } = render(<Metric height="30vw" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100%",
+                height: "30vw",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 100px", async () => {
+        const { container } = render(<Metric width="100px" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100px",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30em", async () => {
+        const { container } = render(<Metric width="30em" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30em",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30%", async () => {
+        const { container } = render(<Metric width="30%" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30%",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30vh", async () => {
+        const { container } = render(<Metric width="30vh" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30vh",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30vw", async () => {
+        const { container } = render(<Metric width="30vw" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30vw",
+                position: "relative",
+                display: "inline-block",
+            });
+        });
+    });
+
+
     it("processes type prop correctly when type is none (string)", async () => {
         const { container } = render(<Metric type="none" />);
         await waitFor(() => {
@@ -295,7 +421,7 @@ describe("Metric Component", () => {
             expect(angularAxis).not.toBeInTheDocument();
         });
     });
-    
+
     it("processes type prop correctly when type is None", async () => {
         const { container } = render(<Metric type="None" />);
         await waitFor(() => {
@@ -315,8 +441,8 @@ describe("Metric Component", () => {
 
         const { container } = render(
             <ThemeProvider theme={darkTheme}>
-                <Metric template_Dark_={JSON.stringify(darkTemplate)}  />
-            </ThemeProvider>
+                <Metric template_Dark_={JSON.stringify(darkTemplate)} />
+            </ThemeProvider>,
         );
         await waitFor(() => {
             const elt = container.querySelector(".main-svg");
@@ -335,8 +461,8 @@ describe("Metric Component", () => {
 
         const { container } = render(
             <ThemeProvider theme={lightTheme}>
-                <Metric template_Light_={JSON.stringify(lightTemplate)}  />
-            </ThemeProvider>
+                <Metric template_Light_={JSON.stringify(lightTemplate)} />
+            </ThemeProvider>,
         );
         await waitFor(() => {
             const elt = container.querySelector(".main-svg");
@@ -348,7 +474,7 @@ describe("Metric Component", () => {
 
     it.skip("logs an error when template_Dark_ prop is not a valid JSON string", () => {
         const consoleSpy = jest.spyOn(console, "info");
-        render(<Metric template_Dark_="not a valid JSON string"/>);
+        render(<Metric template_Dark_="not a valid JSON string" />);
         expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
         consoleSpy.mockRestore();
     }); // TODO: Not working at the moment, need to fix

+ 35 - 18
frontend/taipy-gui/src/components/Taipy/Metric.tsx

@@ -56,6 +56,7 @@ const emptyLayout = {} as Partial<Layout>;
 const defaultStyle = { position: "relative", display: "inline-block", width: "100%" } as CSSProperties;
 const skeletonStyle = { ...defaultStyle, minHeight: "7em" };
 const plotConfig = { displaylogo: false };
+const boxStyle = { height: "100vh" };
 
 const Metric = (props: MetricProps) => {
     const { showValue = true } = props;
@@ -92,17 +93,17 @@ const Metric = (props: MetricProps) => {
         delta !== undefined && mode.push("delta");
         const deltaIncreasing = props.deltaColor
             ? {
-                  color: props.deltaColor == "invert" ? "#FF4136" : props.deltaColor,
-              }
+                color: props.deltaColor == "invert" ? "#FF4136" : props.deltaColor,
+            }
             : undefined;
         const deltaDecreasing =
             props.deltaColor == "invert"
                 ? {
-                      color: "#3D9970",
-                  }
+                    color: "#3D9970",
+                }
                 : props.negativeDeltaColor
-                  ? { color: props.negativeDeltaColor }
-                  : undefined;
+                    ? { color: props.negativeDeltaColor }
+                    : undefined;
         return [
             {
                 domain: { x: [0, 1], y: [0, 1] },
@@ -158,8 +159,6 @@ const Metric = (props: MetricProps) => {
     const layout = useMemo(() => {
         const layout = {
             ...baseLayout,
-            height: baseLayout.height !== undefined ? baseLayout.height : props.height,
-            width: baseLayout.width !== undefined ? baseLayout.width : props.width,
         };
         let template = undefined;
         try {
@@ -185,8 +184,6 @@ const Metric = (props: MetricProps) => {
         return layout as Partial<Layout>;
     }, [
         props.title,
-        props.height,
-        props.width,
         props.template,
         props.template_Dark_,
         props.template_Light_,
@@ -194,15 +191,35 @@ const Metric = (props: MetricProps) => {
         baseLayout,
     ]);
 
+    const style = useMemo(() => {
+        const normalizeSize = (val: string | number | undefined): string | undefined => {
+            if (typeof val === "number" || (typeof val === "string" && /^\d+$/.test(val))) {
+                return `${val}px`;
+            }
+            return val;
+        };
+
+        const width = props.width ? normalizeSize(props.width) : "100%";
+        const height = props.height ? normalizeSize(props.height) : undefined;
+
+        return { ...defaultStyle, width, height };
+    }, [props.width, props.height]);
+
     return (
-        <Tooltip title={hover || ""}>
-            <Box className={`${className} ${getComponentClassName(props.children)}`}>
-                <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
-                    <Plot data={data} layout={layout} style={defaultStyle} config={plotConfig} useResizeHandler />
-                </Suspense>
-                {props.children}
-            </Box>
-        </Tooltip>
+            <Tooltip title={hover || ""}>
+                <Box className={`${className} ${getComponentClassName(props.children)}`} style={boxStyle}>
+                    <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
+                        <Plot
+                            data={data}
+                            layout={layout}
+                            style={style}
+                            config={plotConfig}
+                            useResizeHandler
+                        />
+                    </Suspense>
+                    {props.children}
+                </Box>
+            </Tooltip>
     );
 };
 

+ 1 - 3
taipy/gui_core/_context.py

@@ -639,11 +639,10 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     client_id=self.gui._get_client_id(),
                     module_context=self.gui._get_locals_context(),
                 )
-                client_status = _ClientStatus(self.gui._get_client_id(), submission_entity.submission_status)
+                client_status = _ClientStatus(self.gui._get_client_id(), None)
                 with self.submissions_lock:
                     self.client_submission[submission_entity.id] = client_status
                 if Config.core.mode == "development":
-                    client_status.submission_status = SubmissionStatus.SUBMITTED
                     self.submission_status_callback(submission_entity.id)
                 _GuiCoreContext.__assign_var(state, error_var, "")
         except Exception as e:
@@ -715,7 +714,6 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     args.append(None)
         return args
 
-
     def get_sorted_datanode_list(
         self,
         entities: t.Union[

+ 1 - 1
taipy/gui_core/_utils.py

@@ -17,4 +17,4 @@ from taipy.core.submission.submission_status import SubmissionStatus
 @dataclass
 class _ClientStatus:
     client_id: t.Optional[str]
-    submission_status: SubmissionStatus
+    submission_status: t.Optional[SubmissionStatus]

+ 2 - 2
tests/gui_core/test_context_is_readable.py

@@ -25,7 +25,7 @@ from taipy.core.data.pickle import PickleDataNode
 from taipy.core.job._job_manager_factory import _JobManagerFactory
 from taipy.core.scenario._scenario_manager_factory import _ScenarioManagerFactory
 from taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
-from taipy.core.submission.submission import Submission, SubmissionStatus
+from taipy.core.submission.submission import Submission
 from taipy.core.task._task_manager_factory import _TaskManagerFactory
 from taipy.gui import Gui, State
 from taipy.gui_core._context import _GuiCoreContext
@@ -204,7 +204,7 @@ class TestGuiCoreContext_is_readable:
             mockGui._get_authorization = lambda s: contextlib.nullcontext()
             gui_core_context = _GuiCoreContext(mockGui)
 
-            gui_core_context.client_submission[a_submission.id] = _ClientStatus("client_id", SubmissionStatus.UNDEFINED)
+            gui_core_context.client_submission[a_submission.id] = _ClientStatus("client_id", None)
             gui_core_context.submission_status_callback(a_submission.id)
             mockget.assert_called()
             found = False