Skip to content

Commit

Permalink
Merge branch 'develop' into bug/#1911-current-page-menu
Browse files Browse the repository at this point in the history
  • Loading branch information
namnguyen20999 committed Oct 12, 2024
2 parents d2704a3 + 6a50d5a commit dde0382
Show file tree
Hide file tree
Showing 20 changed files with 338 additions and 158 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ the community that you are working on it.
### Contribution workflow

1. Make your [own fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) of the repository
target by the issue. Clone it on our local machine, then go inside the directory.
targeted by the issue. Clone it on your local machine, then go inside the directory.

2. We are working with [Pipenv](https://github.com/pypa/pipenv) for our virtualenv.
Create a local env and install development package by running `$ pipenv install --dev`, then run tests with
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The following table summarizes the supported and maintained versions of Taipy th

| Version | Supported |
| ------- | ------------------ |
| < 2.0 | :x: |
| >= 2.0 | :white_check_mark: |
| < 3.0 | :x: |
| >= 3.0 | :white_check_mark: |


## Reporting a Vulnerability
Expand Down
2 changes: 1 addition & 1 deletion frontend/taipy-gui/base/src/wsAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class TaipyWsAdapter extends WsAdapter {
for (const muPayload of message.payload as [MultipleUpdatePayload]) {
const encodedName = muPayload.name;
const { value } = muPayload.payload;
if (value && typeof (value as any).__taipy_refresh === "boolean") {
if (value && (value as any).__taipy_refresh !== undefined) {
// refresh all requested data for this encodedName var
const requestDataOptions = taipyApp.variableData?._requested_data[encodedName];
for (const dataKey in requestDataOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
const baseColumns = useDynamicJsonProperty(props.columns, props.defaultColumns, defaultColumns);

const refresh = props.data && typeof props.data.__taipy_refresh === "boolean";
const refresh = props.data?.__taipy_refresh !== undefined;

useEffect(() => {
if (!refresh && props.data && page.current.key && props.data[page.current.key] !== undefined) {
Expand Down
26 changes: 14 additions & 12 deletions frontend/taipy-gui/src/components/Taipy/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
* specific language governing permissions and limitations under the License.
*/

import React, { CSSProperties, useCallback, useEffect, useMemo, useRef, useState, lazy, Suspense } from "react";
import React, { CSSProperties, lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";

import { useTheme } from "@mui/material";
import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton";
import Tooltip from "@mui/material/Tooltip";
import { nanoid } from "nanoid";
import {
Config,
Data,
Expand All @@ -23,18 +29,15 @@ import {
PlotSelectionEvent,
ScatterLine,
} from "plotly.js";
import Skeleton from "@mui/material/Skeleton";
import Box from "@mui/material/Box";
import Tooltip from "@mui/material/Tooltip";
import { useTheme } from "@mui/material";
import { Figure } from "react-plotly.js";

import { getArrayValue, getUpdateVar, TaipyActiveProps, TaipyChangeProps } from "./utils";
import {
createRequestChartUpdateAction,
createSendActionNameAction,
createSendUpdateAction,
} from "../../context/taipyReducers";
import { ColumnDesc } from "./tableUtils";
import { lightenPayload } from "../../context/wsUtils";
import { darkThemeTemplate } from "../../themes/darkThemeTemplate";
import {
useClassNames,
useDispatch,
Expand All @@ -43,10 +46,9 @@ import {
useDynamicProperty,
useModule,
} from "../../utils/hooks";
import { darkThemeTemplate } from "../../themes/darkThemeTemplate";
import { Figure } from "react-plotly.js";
import { lightenPayload } from "../../context/wsUtils";
import { ColumnDesc } from "./tableUtils";
import { getComponentClassName } from "./TaipyStyle";
import { getArrayValue, getUpdateVar, TaipyActiveProps, TaipyChangeProps } from "./utils";

const Plot = lazy(() => import("react-plotly.js"));

Expand Down Expand Up @@ -298,7 +300,7 @@ const Chart = (props: ChartProp) => {
const theme = useTheme();
const module = useModule();

const refresh = typeof data.__taipy_refresh === "boolean";
const refresh = useMemo(() => data?.__taipy_refresh !== undefined ? nanoid() : false, [data]);
const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
const active = useDynamicProperty(props.active, props.defaultActive, true);
const render = useDynamicProperty(props.render, props.defaultRender, true);
Expand Down Expand Up @@ -444,7 +446,7 @@ const Chart = (props: ChartProp) => {
if (props.figure) {
return lastDataPl.current;
}
if (typeof data === "number" && lastDataPl.current) {
if (data.__taipy_refresh !== undefined && lastDataPl.current) {
return lastDataPl.current;
}
const datum = data[dataKey];
Expand Down
3 changes: 1 addition & 2 deletions frontend/taipy-gui/src/components/Taipy/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,7 @@ const Chat = (props: ChatProps) => {
setShowMessage(false);
}, []);

// const refresh = typeof props.messages === "number";
const refresh = props.messages && typeof props.messages.__taipy_refresh === "boolean";
const refresh = props.messages?.__taipy_refresh !== undefined;

useEffect(() => {
if (!refresh && props.messages && page.current.key && props.messages[page.current.key] !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
const formatConfig = useFormatConfig();
const module = useModule();

const refresh = props.data && typeof props.data.__taipy_refresh === "boolean";
const refresh = props.data?.__taipy_refresh !== undefined;
const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
const active = useDynamicProperty(props.active, props.defaultActive, true);
const editable = useDynamicProperty(props.editable, props.defaultEditable, false);
Expand Down
20 changes: 12 additions & 8 deletions frontend/taipy/src/ScenarioSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,18 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
<DialogActions>
<Stack direction="row" justifyContent="space-between" sx={ActionContentSx}>
{actionEdit && (
<Button
variant="outlined"
color="error"
onClick={onConfirmDialogOpen}
disabled={!scenario || !scenario[ScFProps.deletable]}
>
Delete
</Button>
<Tooltip title={scenario && scenario[ScFProps.deletable]}>
<span>
<Button
variant="outlined"
color="error"
onClick={onConfirmDialogOpen}
disabled={!scenario || !!scenario[ScFProps.deletable]}
>
Delete
</Button>
</span>
</Tooltip>
)}
<Button variant="outlined" color="inherit" onClick={close}>
Cancel
Expand Down
29 changes: 29 additions & 0 deletions taipy/gui/_event_context_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

import typing as t
from threading import Thread


class _EventManager:
def __init__(self) -> None:
self.__thread_stack: t.List[Thread] = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
if self.__thread_stack:
self.__thread_stack.pop().start()
return self

def _add_thread(self, thread: Thread):
self.__thread_stack.append(thread)
2 changes: 1 addition & 1 deletion taipy/gui/_gui_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class _GuiCLI(_AbstractCLI):
"const": "",
"help": "Specify server host",
},
("--client-url", "-H"): {
("--client-url",): {
"dest": "taipy_client_url",
"metavar": "CLIENT_URL",
"nargs": "?",
Expand Down
2 changes: 1 addition & 1 deletion taipy/gui/_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _resolve_hook(*args, **kwargs):
func = getattr(hook, name)
if not callable(func):
raise Exception(f"'{name}' hook is not callable")
res = getattr(hook, name)(*args, **kwargs)
res = func(*args, **kwargs)
except Exception as e:
_TaipyLogger._get_logger().error(f"Error while calling hook '{name}': {e}")
return
Expand Down
42 changes: 23 additions & 19 deletions taipy/gui/_renderers/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import xml.etree.ElementTree as etree
from datetime import date, datetime, time
from enum import Enum
from inspect import isclass, isroutine
from inspect import isclass
from types import LambdaType
from urllib.parse import quote

Expand All @@ -34,7 +34,9 @@
_getscopeattr,
_getscopeattr_drill,
_is_boolean,
_is_function,
_is_true,
_is_unnamed_function,
_MapDict,
_to_camel_case,
)
Expand Down Expand Up @@ -140,7 +142,7 @@ def __parse_attribute_value(gui: "Gui", value) -> t.Tuple:
hash_value = gui._evaluate_expr(value)
try:
func = gui._get_user_function(hash_value)
if isroutine(func):
if _is_function(func):
return (func, hash_value)
return (_getscopeattr_drill(gui, hash_value), hash_value)
except AttributeError:
Expand All @@ -163,10 +165,10 @@ def _get_variable_hash_names(
(val, hash_name) = _Builder.__parse_attribute_value(gui, v.replace('\\"', '"'))
else:
val = v
if isroutine(val) and not hash_name:
if _is_function(val) and not hash_name:
# if it's not a callable (and not a string), forget it
if val.__name__ == "<lambda>":
# if it is a lambda and it has already a hash_name, we're fine
if _is_unnamed_function(val):
# lambda or callable instance
hash_name = _get_lambda_id(t.cast(LambdaType, val))
gui._bind_var_val(hash_name, val) # type: ignore[arg-type]
else:
Expand Down Expand Up @@ -294,7 +296,7 @@ def set_number_attribute(self, name: str, default_value: t.Optional[str] = None,
except ValueError:
raise ValueError(f"Property {name} expects a number for control {self.__control_type}") from None
elif isinstance(value, numbers.Number):
val = value # type: ignore
val = value # type: ignore[assignment]
else:
raise ValueError(
f"Property {name} expects a number for control {self.__control_type}, received {type(value)}"
Expand Down Expand Up @@ -347,7 +349,7 @@ def __set_function_attribute(
if not optional:
_warn(f"Property {name} is required for control {self.__control_type}.")
return self
elif isroutine(str_attr):
elif _is_function(str_attr):
str_attr = self.__hashes.get(name)
if str_attr is None:
return self
Expand Down Expand Up @@ -403,12 +405,12 @@ def _get_lov_adapter( # noqa: C901
adapter = self.__attributes.get("adapter", adapter)
if adapter and isinstance(adapter, str):
adapter = self.__gui._get_user_function(adapter)
if adapter and not callable(adapter):
if adapter and not _is_function(adapter):
_warn(f"{self.__element_name}: adapter property value is invalid.")
adapter = None
var_type = self.__attributes.get("type", var_type)
if isclass(var_type):
var_type = var_type.__name__ # type: ignore
var_type = var_type.__name__

if isinstance(lov, list):
if not isinstance(var_type, str):
Expand All @@ -425,10 +427,10 @@ def _get_lov_adapter( # noqa: C901
var_type = self.__gui._get_unique_type_adapter(type(elt).__name__)
if adapter is None:
adapter = self.__gui._get_adapter_for_type(var_type)
elif var_type == str.__name__ and isroutine(adapter):
elif var_type == str.__name__ and _is_function(adapter):
var_type += (
_get_lambda_id(t.cast(LambdaType, adapter))
if adapter.__name__ == "<lambda>"
if _is_unnamed_function(adapter)
else _get_expr_var_name(adapter.__name__)
)
if lov_name:
Expand All @@ -442,13 +444,15 @@ def _get_lov_adapter( # noqa: C901
else:
self.__gui._add_type_for_var(value_name, t.cast(str, var_type))
if adapter is not None:
self.__gui._add_adapter_for_type(var_type, adapter) # type: ignore
self.__gui._add_adapter_for_type(var_type, adapter) # type: ignore[arg-type]

if default_lov is not None and lov:
for elt in lov:
ret = self.__gui._run_adapter(
t.cast(t.Callable, adapter), elt, adapter.__name__ if isroutine(adapter) else "adapter"
) # type: ignore
t.cast(t.Callable, adapter),
elt,
adapter.__name__ if hasattr(adapter, "__name__") else "adapter",
)
if ret is not None:
default_lov.append(ret)

Expand All @@ -459,9 +463,9 @@ def _get_lov_adapter( # noqa: C901
ret = self.__gui._run_adapter(
t.cast(t.Callable, adapter),
val,
adapter.__name__ if isroutine(adapter) else "adapter",
adapter.__name__ if hasattr(adapter, "__name__") else "adapter",
id_only=True,
) # type: ignore
)
if ret is not None:
ret_list.append(ret)
if multi_selection:
Expand Down Expand Up @@ -575,7 +579,7 @@ def _get_dataframe_attributes(self) -> "_Builder":
if not isinstance(self.__attributes.get("style"), (type(None), dict, _MapDict)):
_warn("Table: property 'style' has been renamed to 'row_class_name'.")
if row_class_name := self.__attributes.get("row_class_name"):
if isroutine(row_class_name):
if _is_function(row_class_name):
value = self.__hashes.get("row_class_name")
elif isinstance(row_class_name, str):
value = row_class_name.strip()
Expand All @@ -586,7 +590,7 @@ def _get_dataframe_attributes(self) -> "_Builder":
elif value:
self.set_attribute("rowClassName", value)
if tooltip := self.__attributes.get("tooltip"):
if isroutine(tooltip):
if _is_function(tooltip):
value = self.__hashes.get("tooltip")
elif isinstance(tooltip, str):
value = tooltip.strip()
Expand Down Expand Up @@ -806,7 +810,7 @@ def __set_default_value(
elif var_type == PropertyType.lov_value:
# Done by _get_adapter
return self
elif isclass(var_type) and issubclass(var_type, _TaipyBase): # type: ignore
elif isclass(var_type) and issubclass(var_type, _TaipyBase):
return self.__set_default_value(var_name, t.cast(t.Callable, var_type)(value, "").get())
else:
return self.__set_json_attribute(default_var_name, value)
Expand Down
Loading

0 comments on commit dde0382

Please sign in to comment.