Преглед изворни кода

support a list of Icon as Users (#2590)

* support a list of Icon as Users
resolves #2583

* support emoji

* linter

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide пре 2 недеља
родитељ
комит
9875328ba8

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

@@ -23,6 +23,7 @@ import { stringIcon } from "../../utils/icon";
 import { TableValueType } from "./tableUtils";
 
 import { toDataUrl } from "../../utils/image";
+import { LoVElt } from "./lovUtils";
 jest.mock('../../utils/image', () => ({
     toDataUrl: (url: string) => new Promise((resolve) => resolve(url)),
   }));
@@ -40,9 +41,10 @@ const messages: TableValueType = {
         start: 0,
     },
 };
-const user1: [string, stringIcon] = ["Fred", { path: "/images/favicon.png", text: "Fred.png" }];
+const user1: [string, stringIcon] = ["Fred", { path: "/images/favicon.png", text: "Fred" }];
 const user2: [string, stringIcon] = ["Fredi", { path: "/images/fred.png", text: "Fredi.png" }];
 const users = [user1, user2];
+const users2 = [["<taipy.gui.icon.Icon 1", user1[1]], ["<taipy.gui.icon.Icon 2", user2[1]]] as LoVElt[];
 
 const searchMsg = messages[valueKey].data[0][1];
 
@@ -65,7 +67,12 @@ describe("Chat Component", () => {
     });
     it("can display an avatar", async () => {
         const { getByAltText } = render(<Chat messages={messages} users={users} defaultKey={valueKey} mode="raw" />);
-        const elt = getByAltText("Fred.png");
+        const elt = getByAltText("Fred");
+        expect(elt.tagName).toBe("IMG");
+    });
+    it("can display an avatar from Icon", async () => {
+        const { getByAltText } = render(<Chat messages={messages} users={users2} defaultKey={valueKey} mode="raw" />);
+        const elt = getByAltText("Fred");
         expect(elt.tagName).toBe("IMG");
     });
     it("is disabled", async () => {

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

@@ -368,7 +368,8 @@ const Chat = (props: ChatProps) => {
     const avatars = useMemo(() => {
         return users.reduce((pv, elt) => {
             if (elt.id) {
-                pv[elt.id] =
+                const id = elt.id.startsWith("<taipy.gui.icon.Icon") && typeof elt.item !== "string" ? elt.item.text : elt.id;
+                pv[id] =
                     typeof elt.item == "string" ? (
                         <Tooltip title={elt.item}>
                             <Avatar sx={chatAvatarSx}>{getInitials(elt.item)}</Avatar>

+ 1 - 1
frontend/taipy-gui/src/utils/index.ts

@@ -206,7 +206,7 @@ export const formatWSValue = (
 export const getInitials = (value: string, max = 2): string =>
     (value || "")
         .split(" ", max)
-        .map((word) => (word.length ? word.charAt(0) : ""))
+        .map((word) => (word.length ? String.fromCodePoint(word.codePointAt(0) || 0) : ""))
         .join("")
         .toUpperCase();
 

+ 26 - 23
tools/coverage_check.py

@@ -10,19 +10,20 @@
 # specific language governing permissions and limitations under the License.
 
 import argparse
-import xmltodict
-import sys
 import subprocess
+import sys
+
+import xmltodict
 
 
 def check_total_coverage(coverage_file, threshold=80):
     """Check the total project coverage."""
     with open(coverage_file) as f:
         data = xmltodict.parse(f.read())
-    total_coverage = float(data['coverage']['@line-rate']) * 100
-    print(f"Total Coverage: {total_coverage:.2f}%")
+    total_coverage = float(data["coverage"]["@line-rate"]) * 100
+    print(f"Total Coverage: {total_coverage:.2f}%")  # noqa: T201
     if total_coverage < threshold:
-        print(f"Total project coverage is below {threshold}%: {total_coverage:.2f}%")
+        print(f"Total project coverage is below {threshold}%: {total_coverage:.2f}%")  # noqa: T201
         sys.exit(1)
 
 
@@ -49,56 +50,58 @@ def check_changed_files_coverage(coverage_file, changed_files, threshold=80):
     for file in changed_files:
         if file in files:
             coverage = files[file]
-            print(f"Coverage for {file}: {coverage:.2f}%")
+            print(f"Coverage for {file}: {coverage:.2f}%")  # noqa: T201
             sum_coverage += coverage
             qty += 1
         else:
-            print(f"No coverage data found for {file}")
+            print(f"No coverage data found for {file}")  # noqa: T201
 
     if qty:
-        if sum_coverage/qty < threshold:
-            print(f"Coverage for changed files is below {threshold}%: {sum_coverage/qty:.2f}%")
+        if sum_coverage / qty < threshold:
+            print(f"Coverage for changed files is below {threshold}%: {sum_coverage/qty:.2f}%")  # noqa: T201
             sys.exit(1)
-        print(f"Coverage for changed files: {sum_coverage/qty:.2f}%")
+        print(f"Coverage for changed files: {sum_coverage/qty:.2f}%")  # noqa: T201
     else:
-        print("No file detected to run coverage for.")
+        print("No file detected to run coverage for.")  # noqa: T201
 
 
 def get_changed_files(base_branch):
     """Get the list of changed Python files in the pull request."""
     try:
         result = subprocess.run(
-            ['git', 'diff', '--name-only', f"origin/{base_branch}", '--', '*.py'],
+            ["git", "diff", "--name-only", f"origin/{base_branch}", "--", "*.py"],
             capture_output=True,
             text=True,
             check=True,
         )
         changed_files = [
-            file.replace("taipy/", "") for file in result.stdout.strip().splitlines() if not file.startswith(('tests/', 'tools/'))
+            file.replace("taipy/", "")
+            for file in result.stdout.strip().splitlines()
+            if not file.startswith(("tests/", "tools/"))
         ]
         return changed_files
     except subprocess.CalledProcessError as e:
-        print(f"Error fetching changed files: {e}")
+        print(f"Error fetching changed files: {e}") # noqa: T201
         sys.exit(1)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Coverage check script.")
-    parser.add_argument('command', choices=['check-total', 'check-changed'], help="Command to execute")
-    parser.add_argument('--coverage-file', default='coverage.xml', help="Path to the coverage XML file")
-    parser.add_argument('--threshold', type=float, default=80, help="Coverage threshold percentage")
-    parser.add_argument('--base-branch', help="Base branch for comparing changed files")
+    parser.add_argument("command", choices=["check-total", "check-changed"], help="Command to execute")
+    parser.add_argument("--coverage-file", default="coverage.xml", help="Path to the coverage XML file")
+    parser.add_argument("--threshold", type=float, default=80, help="Coverage threshold percentage")
+    parser.add_argument("--base-branch", help="Base branch for comparing changed files")
 
     args = parser.parse_args()
 
-    if args.command == 'check-total':
+    if args.command == "check-total":
         check_total_coverage(args.coverage_file, args.threshold)
-    elif args.command == 'check-changed':
+    elif args.command == "check-changed":
         if not args.base_branch:
-            print("Error: --base-branch is required for check-changed")
+            print("Error: --base-branch is required for check-changed") # noqa: T201
             sys.exit(1)
         changed_files = get_changed_files(args.base_branch)
         if not changed_files:
-            print("No relevant Python files changed.")
+            print("No relevant Python files changed.") # noqa: T201
             sys.exit(0)
         check_changed_files_coverage(args.coverage_file, changed_files, args.threshold)

+ 5 - 4
tools/gui/generate_pyi.py

@@ -81,9 +81,10 @@ with open(gui_pyi_file, "w", encoding="utf-8") as write_file:
 # Generate Page Builder pyi file (gui/builder/__init__.pyi)
 # ##################################################################################################
 # Types that appear in viselements.json
-from taipy.gui import Icon  # noqa: E402
-from taipy.core import Cycle, DataNode, Job, Scenario  # noqa: E402
-from datetime import datetime
+from datetime import datetime  # noqa: E402, F401
+
+from taipy.core import Cycle, DataNode, Job, Scenario  # noqa: E402, F401
+from taipy.gui import Icon  # noqa: E402, F401
 
 # Read the version
 current_version = "latest"
@@ -187,7 +188,7 @@ def format_as_parameter(property: Dict[str, str], element_name: str):
         elif hasattr(type_desc, "__name__") and type_desc.__name__ not in ["str", "Any"]:
             type = f"Union[{type}, str]"
     except NameError:
-        print(f"WARNING - Couldn't parse type '{type}' in {element_name}.{name}")
+        print(f"WARNING - Couldn't parse type '{type}' in {element_name}.{name}") # noqa: T201
 
     if default_value is None or default_value == "None":
         default_value = " = None"

+ 1 - 1
tools/release/common.py

@@ -20,7 +20,7 @@ import typing as t
 from dataclasses import asdict, dataclass
 from datetime import datetime
 from pathlib import Path
-from functools import total_ordering
+
 import requests