Browse Source

tabular datanode edit in viewer (#1705)

* tabular datanode edit in viewer
resolves #1613

* spell

* can't raise this

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 8 months ago
parent
commit
4c4ff12a2f

+ 56 - 57
frontend/taipy-gui/package-lock.json

@@ -191,9 +191,9 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.25.4",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.4.tgz",
-      "integrity": "sha512-NFtZmZsyzDPJnk9Zg3BbTfKKc9UlHYzD0E//p2Z3B9nCwwtJW9T0gVbCz8+fBngnn4zf1Dr3IK8PHQQHq0lDQw==",
+      "version": "7.25.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
+      "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
       "dependencies": {
         "@babel/types": "^7.25.4",
         "@jridgewell/gen-mapping": "^0.3.5",
@@ -2097,14 +2097,14 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.13.0.tgz",
-      "integrity": "sha512-cmpAfkzOjUgL4I8WenU4elm1QJO8vWpGmIPCezT3Q9wFjGL1QApQhJ5gMZ+X4tM6Gha9AhIWNQX5eXHKbSoyFQ==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.14.0.tgz",
+      "integrity": "sha512-3xI3xYVxqPU4//KfE4FcR+Zs7UT4kkDPvA+IDOcQdRUyVwmcXCjBuJZgKgJMqSCNK/KIJZQQrpmy5XGHOKTbdA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/system": "^5.16.5",
-        "@mui/utils": "^5.16.5",
-        "@types/react-transition-group": "^4.4.10",
+        "@mui/system": "^5.16.7",
+        "@mui/utils": "^5.16.6",
+        "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
         "react-transition-group": "^4.4.5"
@@ -2161,12 +2161,12 @@
       }
     },
     "node_modules/@mui/x-internals": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.13.0.tgz",
-      "integrity": "sha512-eUK7iykkDWU+wBfTzE/S0qh4awgVgsORfrpvuPbUp+E6qUj1Xhu9M/WKzbwz0CPFnTJZwBQ9KYrxpGXnPBEpRQ==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.14.0.tgz",
+      "integrity": "sha512-+qWIHLgt2vgH6bKmf7IwRvS86UbZRWKAdDY/yTQJaqzCzyesUvQhD+WRxe1kpdCK8UE061S9/Ju7hLkM4kjRNA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/utils": "^5.16.5"
+        "@mui/utils": "^5.16.6"
       },
       "engines": {
         "node": ">=14.0.0"
@@ -2180,15 +2180,15 @@
       }
     },
     "node_modules/@mui/x-tree-view": {
-      "version": "7.13.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.13.0.tgz",
-      "integrity": "sha512-ADixvp85a0iZ7AOzBuCPQ+yl+gMq0BlIWhg3GfbX+57sMhjcdOEUUxcGIcIt6pw1V05bVXE2/QP+5qzDamiGPw==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.14.0.tgz",
+      "integrity": "sha512-j1sK0tLrsiCu0FxwTJQkVm2nbLYc1tRLwmPDAXcQ3nuzGDzn0x/IA28dBjxse/+oNy4j2cpJz3k/mSz/a4ZLjA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/system": "^5.16.5",
-        "@mui/utils": "^5.16.5",
-        "@mui/x-internals": "7.13.0",
-        "@types/react-transition-group": "^4.4.10",
+        "@mui/system": "^5.16.7",
+        "@mui/utils": "^5.16.6",
+        "@mui/x-internals": "7.14.0",
+        "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
         "react-transition-group": "^4.4.5"
@@ -2490,13 +2490,12 @@
       }
     },
     "node_modules/@testing-library/jest-dom": {
-      "version": "6.4.8",
-      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz",
-      "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==",
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz",
+      "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==",
       "dev": true,
       "dependencies": {
         "@adobe/css-tools": "^4.4.0",
-        "@babel/runtime": "^7.9.2",
         "aria-query": "^5.0.0",
         "chalk": "^3.0.0",
         "css.escape": "^1.5.1",
@@ -2760,9 +2759,9 @@
       }
     },
     "node_modules/@types/eslint": {
-      "version": "8.56.11",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
-      "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==",
+      "version": "8.56.12",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
+      "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
@@ -3950,9 +3949,9 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.7.4",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
-      "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
+      "version": "1.7.5",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
+      "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
@@ -4340,9 +4339,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001651",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
-      "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
+      "version": "1.0.30001653",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz",
+      "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==",
       "funding": [
         {
           "type": "opencollective",
@@ -4544,9 +4543,9 @@
       }
     },
     "node_modules/cjs-module-lexer": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
-      "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz",
+      "integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==",
       "dev": true
     },
     "node_modules/clamp": {
@@ -5957,9 +5956,9 @@
       }
     },
     "node_modules/emoji-regex": {
-      "version": "10.3.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
-      "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
+      "version": "10.4.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
       "dev": true
     },
     "node_modules/end-of-stream": {
@@ -12041,9 +12040,9 @@
       ]
     },
     "node_modules/micromatch": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
-      "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "dev": true,
       "dependencies": {
         "braces": "^3.0.3",
@@ -15015,20 +15014,20 @@
       }
     },
     "node_modules/ts-jest": {
-      "version": "29.2.4",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz",
-      "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==",
+      "version": "29.2.5",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
+      "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
       "dev": true,
       "dependencies": {
-        "bs-logger": "0.x",
+        "bs-logger": "^0.2.6",
         "ejs": "^3.1.10",
-        "fast-json-stable-stringify": "2.x",
+        "fast-json-stable-stringify": "^2.1.0",
         "jest-util": "^29.0.0",
         "json5": "^2.2.3",
-        "lodash.memoize": "4.x",
-        "make-error": "1.x",
-        "semver": "^7.5.3",
-        "yargs-parser": "^21.0.1"
+        "lodash.memoize": "^4.1.2",
+        "make-error": "^1.3.6",
+        "semver": "^7.6.3",
+        "yargs-parser": "^21.1.1"
       },
       "bin": {
         "ts-jest": "cli.js"
@@ -15162,9 +15161,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
-      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+      "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
     },
     "node_modules/type": {
       "version": "2.7.3",
@@ -15314,9 +15313,9 @@
       }
     },
     "node_modules/typedoc-plugin-markdown": {
-      "version": "4.2.5",
-      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.5.tgz",
-      "integrity": "sha512-ZWIfc0OqwEtQfuaqbmM1kesMi/Fhc++W+5f3TDEm1Tsi28pHSoZk4WCOm4lNuN30WtEImwAHhhXC4DIWki1DiA==",
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.6.tgz",
+      "integrity": "sha512-k33o2lZSGpL3GjH28eW+RsujzCYFP0L5GNqpK+wa4CBcMOxpj8WV7SydNRLS6eSa2UvaPvNVJTaAZ6Tm+8GXoA==",
       "dev": true,
       "engines": {
         "node": ">= 18"

+ 23 - 19
frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx

@@ -228,7 +228,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             const nr = newValue.data as RowType[];
             if (Array.isArray(nr) && nr.length > newValue.start) {
                 setRows(nr);
-                newValue.comp && setCompRows(newValue.comp as RowType[])
+                newValue.comp && setCompRows(newValue.comp as RowType[]);
                 promise && promise.resolve();
             } else {
                 promise && promise.reject();
@@ -279,34 +279,36 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
         if (baseColumns) {
             try {
                 let filter = false;
-                Object.values(baseColumns).forEach((col) => {
-                    if (typeof col.filter != "boolean") {
-                        col.filter = !!props.filter;
+                const newCols: Record<string, ColumnDesc> = {};
+                Object.entries(baseColumns).forEach(([cId, cDesc]) => {
+                    const nDesc = (newCols[cId] = { ...cDesc });
+                    if (typeof nDesc.filter != "boolean") {
+                        nDesc.filter = !!props.filter;
                     }
-                    filter = filter || col.filter;
-                    if (typeof col.notEditable != "boolean") {
-                        col.notEditable = !editable;
+                    filter = filter || nDesc.filter;
+                    if (typeof nDesc.notEditable != "boolean") {
+                        nDesc.notEditable = !editable;
                     }
-                    if (col.tooltip === undefined) {
-                        col.tooltip = props.tooltip;
+                    if (nDesc.tooltip === undefined) {
+                        nDesc.tooltip = props.tooltip;
                     }
                 });
                 addDeleteColumn(
                     (active && editable && (onAdd || onDelete) ? 1 : 0) +
                         (active && filter ? 1 : 0) +
                         (active && downloadable ? 1 : 0),
-                    baseColumns
+                    newCols
                 );
-                const colsOrder = Object.keys(baseColumns).sort(getsortByIndex(baseColumns));
+                const colsOrder = Object.keys(newCols).sort(getsortByIndex(newCols));
                 const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
-                    if (baseColumns[col].style) {
+                    if (newCols[col].style) {
                         pv.styles = pv.styles || {};
-                        pv.styles[baseColumns[col].dfid] = baseColumns[col].style as string;
+                        pv.styles[newCols[col].dfid] = newCols[col].style as string;
                     }
-                    hNan = hNan || !!baseColumns[col].nanValue;
-                    if (baseColumns[col].tooltip) {
+                    hNan = hNan || !!newCols[col].nanValue;
+                    if (newCols[col].tooltip) {
                         pv.tooltips = pv.tooltips || {};
-                        pv.tooltips[baseColumns[col].dfid] = baseColumns[col].tooltip as string;
+                        pv.tooltips[newCols[col].dfid] = newCols[col].tooltip as string;
                     }
                     return pv;
                 }, {});
@@ -314,7 +316,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                     styTt.styles = styTt.styles || {};
                     styTt.styles[LINE_STYLE] = props.lineStyle;
                 }
-                return [colsOrder, baseColumns, styTt.styles, styTt.tooltips, hNan, filter];
+                return [colsOrder, newCols, styTt.styles, styTt.tooltips, hNan, filter];
             } catch (e) {
                 console.info("ATable.columns: " + ((e as Error).message || e));
             }
@@ -407,7 +409,9 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                         afs,
                         compare ? onCompare : undefined,
                         updateVars && getUpdateVar(updateVars, "comparedatas"),
-                        typeof userData == "object" ? (userData as Record<string, Record<string, unknown>>).context : undefined
+                        typeof userData == "object"
+                            ? (userData as Record<string, Record<string, unknown>>).context
+                            : undefined
                     )
                 );
             });
@@ -429,7 +433,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             onCompare,
             dispatch,
             module,
-            userData
+            userData,
         ]
     );
 

+ 2 - 14
frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx

@@ -173,10 +173,6 @@ const styledColumns = JSON.stringify({
     },
 });
 
-const invalidColumns = JSON.stringify({
-    invalid: true,
-});
-
 describe("PaginatedTable Component", () => {
     it("renders", async () => {
         const { getByText } = render(<PaginatedTable data={undefined} defaultColumns={tableColumns} />);
@@ -655,7 +651,7 @@ describe("PaginatedTable Component", () => {
                 col: "int",
                 index: 1,
                 reason: "click",
-                value: undefined
+                value: undefined,
             },
             type: "SEND_ACTION_ACTION",
         });
@@ -692,7 +688,7 @@ describe("PaginatedTable Component", () => {
                 col: "Code",
                 index: 0,
                 reason: "button",
-                value: "button action"
+                value: "button action",
             },
             type: "SEND_ACTION_ACTION",
         });
@@ -714,14 +710,6 @@ describe("PaginatedTable Component", () => {
         const elt = document.querySelector('table[aria-labelledby="tableTitle"]');
         expect(elt).toBeInTheDocument();
     });
-    it("logs error when baseColumns prop is invalid", () => {
-        // Mock console.info to check if it gets called
-        console.info = jest.fn();
-        // Render the component with invalid baseColumns prop
-        render(<PaginatedTable defaultColumns={invalidColumns} />);
-        // Check if console.info was called
-        expect(console.info).toHaveBeenCalled();
-    });
     it("should sort the table in ascending order", async () => {
         await waitFor(() => {
             render(<PaginatedTable data={tableValue} defaultColumns={tableColumns} />);

+ 19 - 17
frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx

@@ -136,34 +136,36 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         if (baseColumns) {
             try {
                 let filter = false;
-                Object.values(baseColumns).forEach((col) => {
-                    if (typeof col.filter != "boolean") {
-                        col.filter = !!props.filter;
+                const newCols: Record<string, ColumnDesc> = {};
+                Object.entries(baseColumns).forEach(([cId, cDesc]) => {
+                    const nDesc = (newCols[cId] = { ...cDesc });
+                    if (typeof nDesc.filter != "boolean") {
+                        nDesc.filter = !!props.filter;
                     }
-                    filter = filter || col.filter;
-                    if (typeof col.notEditable != "boolean") {
-                        col.notEditable = !editable;
+                    filter = filter || nDesc.filter;
+                    if (typeof nDesc.notEditable != "boolean") {
+                        nDesc.notEditable = !editable;
                     }
-                    if (col.tooltip === undefined) {
-                        col.tooltip = props.tooltip;
+                    if (nDesc.tooltip === undefined) {
+                        nDesc.tooltip = props.tooltip;
                     }
                 });
                 addDeleteColumn(
                     (active && editable && (onAdd || onDelete) ? 1 : 0) +
                         (active && filter ? 1 : 0) +
                         (active && downloadable ? 1 : 0),
-                    baseColumns
+                    newCols
                 );
-                const colsOrder = Object.keys(baseColumns).sort(getsortByIndex(baseColumns));
+                const colsOrder = Object.keys(newCols).sort(getsortByIndex(newCols));
                 const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
-                    if (baseColumns[col].style) {
+                    if (newCols[col].style) {
                         pv.styles = pv.styles || {};
-                        pv.styles[baseColumns[col].dfid] = baseColumns[col].style as string;
+                        pv.styles[newCols[col].dfid] = newCols[col].style as string;
                     }
-                    hNan = hNan || !!baseColumns[col].nanValue;
-                    if (baseColumns[col].tooltip) {
+                    hNan = hNan || !!newCols[col].nanValue;
+                    if (newCols[col].tooltip) {
                         pv.tooltips = pv.tooltips || {};
-                        pv.tooltips[baseColumns[col].dfid] = baseColumns[col].tooltip as string;
+                        pv.tooltips[newCols[col].dfid] = newCols[col].tooltip as string;
                     }
                     return pv;
                 }, {});
@@ -171,7 +173,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     styTt.styles = styTt.styles || {};
                     styTt.styles[LINE_STYLE] = props.lineStyle;
                 }
-                return [colsOrder, baseColumns, styTt.styles, styTt.tooltips, hNan, filter];
+                return [colsOrder, newCols, styTt.styles, styTt.tooltips, hNan, filter];
             } catch (e) {
                 console.info("PaginatedTable.columns: ", (e as Error).message || e);
             }
@@ -293,7 +295,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         module,
         compare,
         onCompare,
-        userData
+        userData,
     ]);
 
     const onSort = useCallback(

+ 80 - 91
frontend/taipy/package-lock.json

@@ -56,11 +56,11 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.25.0",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
-      "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
+      "version": "7.25.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
+      "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
       "dependencies": {
-        "@babel/types": "^7.25.0",
+        "@babel/types": "^7.25.4",
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^2.5.1"
@@ -112,11 +112,11 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.25.3",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
-      "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
+      "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
       "dependencies": {
-        "@babel/types": "^7.25.2"
+        "@babel/types": "^7.25.4"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -126,9 +126,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.25.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
-      "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz",
+      "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==",
       "dependencies": {
         "regenerator-runtime": "^0.14.0"
       },
@@ -150,15 +150,15 @@
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.25.3",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
-      "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
+      "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
       "dependencies": {
         "@babel/code-frame": "^7.24.7",
-        "@babel/generator": "^7.25.0",
-        "@babel/parser": "^7.25.3",
+        "@babel/generator": "^7.25.4",
+        "@babel/parser": "^7.25.4",
         "@babel/template": "^7.25.0",
-        "@babel/types": "^7.25.2",
+        "@babel/types": "^7.25.4",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
@@ -167,9 +167,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.25.2",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
-      "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz",
+      "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
       "dependencies": {
         "@babel/helper-string-parser": "^7.24.8",
         "@babel/helper-validator-identifier": "^7.24.7",
@@ -237,14 +237,14 @@
       "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
     },
     "node_modules/@emotion/react": {
-      "version": "11.13.0",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz",
-      "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==",
+      "version": "11.13.3",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz",
+      "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
         "@emotion/babel-plugin": "^11.12.0",
         "@emotion/cache": "^11.13.0",
-        "@emotion/serialize": "^1.3.0",
+        "@emotion/serialize": "^1.3.1",
         "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
         "@emotion/utils": "^1.4.0",
         "@emotion/weak-memoize": "^0.4.0",
@@ -260,13 +260,13 @@
       }
     },
     "node_modules/@emotion/serialize": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz",
-      "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz",
+      "integrity": "sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==",
       "dependencies": {
         "@emotion/hash": "^0.9.2",
         "@emotion/memoize": "^0.9.0",
-        "@emotion/unitless": "^0.9.0",
+        "@emotion/unitless": "^0.10.0",
         "@emotion/utils": "^1.4.0",
         "csstype": "^3.0.2"
       }
@@ -299,9 +299,9 @@
       }
     },
     "node_modules/@emotion/unitless": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz",
-      "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ=="
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+      "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
     },
     "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
       "version": "1.1.0",
@@ -880,14 +880,14 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "7.12.1",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.12.1.tgz",
-      "integrity": "sha512-Zj8kt3SCQbJp1qhMi+A3I4KqB8i5OY2Q11mdOEathFhqN/SQm1sUjIa1G09cGP1dPDgK1a6KM6qJGNtcw/nuWA==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.14.0.tgz",
+      "integrity": "sha512-3xI3xYVxqPU4//KfE4FcR+Zs7UT4kkDPvA+IDOcQdRUyVwmcXCjBuJZgKgJMqSCNK/KIJZQQrpmy5XGHOKTbdA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/system": "^5.16.5",
-        "@mui/utils": "^5.16.5",
-        "@types/react-transition-group": "^4.4.10",
+        "@mui/system": "^5.16.7",
+        "@mui/utils": "^5.16.6",
+        "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
         "react-transition-group": "^4.4.5"
@@ -944,12 +944,12 @@
       }
     },
     "node_modules/@mui/x-internals": {
-      "version": "7.12.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.12.0.tgz",
-      "integrity": "sha512-zgu/JqSXBflSvtzfFN8lNi5Wxw79czBv6V/crOrXqCCOzxAIsrcup2FZlwvXlzetm3otS7o/Tzfo/O5dE68NkA==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.14.0.tgz",
+      "integrity": "sha512-+qWIHLgt2vgH6bKmf7IwRvS86UbZRWKAdDY/yTQJaqzCzyesUvQhD+WRxe1kpdCK8UE061S9/Ju7hLkM4kjRNA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/utils": "^5.16.5"
+        "@mui/utils": "^5.16.6"
       },
       "engines": {
         "node": ">=14.0.0"
@@ -963,15 +963,15 @@
       }
     },
     "node_modules/@mui/x-tree-view": {
-      "version": "7.12.1",
-      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.12.1.tgz",
-      "integrity": "sha512-WEejS6mzKQzwm0vKT5W1XqlHxqIFv0AV/MYDgvru39WwaCUCyip32sjvl7cDNwrsC8CkwyBCaEvNDEE9Jx0BkA==",
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.14.0.tgz",
+      "integrity": "sha512-j1sK0tLrsiCu0FxwTJQkVm2nbLYc1tRLwmPDAXcQ3nuzGDzn0x/IA28dBjxse/+oNy4j2cpJz3k/mSz/a4ZLjA==",
       "dependencies": {
         "@babel/runtime": "^7.25.0",
-        "@mui/system": "^5.16.5",
-        "@mui/utils": "^5.16.5",
-        "@mui/x-internals": "7.12.0",
-        "@types/react-transition-group": "^4.4.10",
+        "@mui/system": "^5.16.7",
+        "@mui/utils": "^5.16.6",
+        "@mui/x-internals": "7.14.0",
+        "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
         "react-transition-group": "^4.4.5"
@@ -1124,25 +1124,15 @@
       "dev": true
     },
     "node_modules/@types/eslint": {
-      "version": "8.56.11",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
-      "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==",
+      "version": "8.56.12",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
+      "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
         "@types/json-schema": "*"
       }
     },
-    "node_modules/@types/eslint-scope": {
-      "version": "3.7.7",
-      "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
-      "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
-      "dev": true,
-      "dependencies": {
-        "@types/eslint": "*",
-        "@types/estree": "*"
-      }
-    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1189,12 +1179,12 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "22.3.0",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.3.0.tgz",
-      "integrity": "sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==",
+      "version": "22.5.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
+      "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==",
       "dev": true,
       "dependencies": {
-        "undici-types": "~6.18.2"
+        "undici-types": "~6.19.2"
       }
     },
     "node_modules/@types/parse-json": {
@@ -1208,9 +1198,9 @@
       "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.3",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
-      "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
+      "version": "18.3.4",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz",
+      "integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -2013,9 +2003,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001651",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
-      "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
+      "version": "1.0.30001653",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz",
+      "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==",
       "dev": true,
       "funding": [
         {
@@ -2354,9 +2344,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.7",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.7.tgz",
-      "integrity": "sha512-6FTNWIWMxMy/ZY6799nBlPtF1DFDQ6VQJ7yyDP27SJNt5lwtQ5ufqVvHylb3fdQefvRcgA3fKcFMJi9OLwBRNw==",
+      "version": "1.5.13",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
+      "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -3628,9 +3618,9 @@
       }
     },
     "node_modules/is-core-module": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
-      "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
+      "version": "2.15.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+      "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
       "dependencies": {
         "hasown": "^2.0.2"
       },
@@ -4251,9 +4241,9 @@
       }
     },
     "node_modules/micromatch": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
-      "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "dev": true,
       "dependencies": {
         "braces": "^3.0.3",
@@ -5596,9 +5586,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
-      "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+      "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
     },
     "node_modules/type-check": {
       "version": "0.4.0",
@@ -5726,9 +5716,9 @@
       }
     },
     "node_modules/undici-types": {
-      "version": "6.18.2",
-      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.18.2.tgz",
-      "integrity": "sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==",
+      "version": "6.19.8",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
       "dev": true
     },
     "node_modules/update-browserslist-db": {
@@ -5784,12 +5774,11 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.93.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz",
-      "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==",
+      "version": "5.94.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
+      "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
       "dev": true,
       "dependencies": {
-        "@types/eslint-scope": "^3.7.3",
         "@types/estree": "^1.0.5",
         "@webassemblyjs/ast": "^1.12.1",
         "@webassemblyjs/wasm-edit": "^1.12.1",
@@ -5798,7 +5787,7 @@
         "acorn-import-attributes": "^1.9.5",
         "browserslist": "^4.21.10",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.17.0",
+        "enhanced-resolve": "^5.17.1",
         "es-module-lexer": "^1.2.1",
         "eslint-scope": "5.1.1",
         "events": "^3.2.0",

+ 2 - 1
frontend/taipy/src/DataNodeTable.tsx

@@ -215,7 +215,8 @@ const DataNodeTable = (props: DataNodeTableProps) => {
                 updateVarName={props.updateVarName}
                 data={props.data}
                 userData={userData}
-                onEdit={tableEdit ? props.onEdit : undefined}
+                onEdit={props.onEdit}
+                editable={tableEdit}
                 filter={true}
                 libClassName="taipy-table"
                 pageSize={25}

+ 5 - 2
frontend/taipy/src/JobSelector.tsx

@@ -77,8 +77,8 @@ interface JobSelectorProps {
     updateJbVars?: string;
 }
 
-// job id, job name, empty list, entity id, entity name, submit id, creation date, status
-type Job = [string, string, [], string, string, string, string, number];
+// job id, job name, empty list, entity id, entity name, submit id, creation date, status, not deletable, not readable, not editable
+type Job = [string, string, [], string, string, string, string, number, string, string, string];
 type Jobs = Array<Job>;
 
 enum JobProps {
@@ -90,6 +90,9 @@ enum JobProps {
     submission_id,
     creation_date,
     status,
+    not_deletable,
+    not_readable,
+    not_editable
 }
 const JobLength = Object.keys(JobProps).length / 2;
 

+ 86 - 70
taipy/gui_core/_context.py

@@ -54,11 +54,13 @@ from taipy.core import submit as core_submit
 from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event, EventOperation
 from taipy.core.notification.notifier import Notifier
+from taipy.core.reason import ReasonCollection
 from taipy.core.submission.submission_status import SubmissionStatus
 from taipy.core.taipy import can_create
 from taipy.gui import Gui, State
 from taipy.gui._warnings import _warn
 from taipy.gui.gui import _DoNotUpdate
+from taipy.gui.utils._map_dict import _MapDict
 
 from ._adapters import (
     CustomScenarioFilter,
@@ -124,7 +126,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         and is_readable(t.cast(SequenceId, event.entity_id))
                         else None
                     )
-                    if sequence and hasattr(sequence, "parent_ids") and sequence.parent_ids:  # type: ignore
+                    if sequence and hasattr(sequence, "parent_ids") and sequence.parent_ids:
                         self.broadcast_core_changed({"scenario": list(sequence.parent_ids)})
             except Exception as e:
                 _warn(f"Access to sequence {event.entity_id} failed", e)
@@ -420,8 +422,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
         if update:
             scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
             if delete:
-                if not is_deletable(scenario_id):
-                    state.assign(error_var, f"Scenario. {scenario_id} is not deletable.")
+                if not (reason := is_deletable(scenario_id)):
+                    state.assign(error_var, f"Scenario. {scenario_id} is not deletable: {_get_reason(reason)}.")
                     return
                 try:
                     core_delete(scenario_id)
@@ -507,12 +509,12 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 if (scenario or user_scenario) and (sel_scenario_var := args[1] if isinstance(args[1], str) else None):
                     try:
                         var_name, _ = gui._get_real_var_name(sel_scenario_var)
-                        self.gui._update_var(var_name, scenario or user_scenario, on_change= args[2])
+                        self.gui._update_var(var_name, scenario or user_scenario, on_change=args[2])
                     except Exception as e:  # pragma: no cover
                         _warn("Can't find value variable name in context", e)
         if scenario:
-            if not is_editable(scenario):
-                state.assign(error_var, f"Scenario {scenario_id or name} is not editable.")
+            if not (reason := is_editable(scenario)):
+                state.assign(error_var, f"Scenario {scenario_id or name} is not editable: {_get_reason(reason)}.")
                 return
             with scenario as sc:
                 sc.properties[_GuiCoreContext.__PROP_ENTITY_NAME] = name
@@ -555,9 +557,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     else:
                         primary = data.get(_GuiCoreContext.__PROP_SCENARIO_PRIMARY)
                         if primary is True:
-                            if not is_promotable(scenario):
+                            if not (reason := is_promotable(scenario)):
                                 _GuiCoreContext.__assign_var(
-                                    state, error_var, f"Scenario {entity_id} is not promotable."
+                                    state, error_var, f"Scenario {entity_id} is not promotable: {_get_reason(reason)}."
                                 )
                                 return
                             set_primary(scenario)
@@ -602,7 +604,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     state,
                     error_var,
                     f"{'Sequence' if sequence else 'Scenario'} {sequence or scenario_id} is not submittable: "
-                    + reason.reasons,
+                    + f"{_get_reason(reason)}.",
                 )
                 return
             if entity:
@@ -809,9 +811,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         job.submit_id,
                         job.creation_date,
                         job.status.value,
-                        is_deletable(job),
-                        is_readable(job),
-                        is_editable(job),
+                        _get_reason(is_deletable(job)),
+                        _get_reason(is_readable(job)),
+                        _get_reason(is_editable(job)),
                     )
         except Exception as e:
             _warn(f"Access to job ({job.id if hasattr(job, 'id') else 'No_id'}) failed", e)
@@ -829,11 +831,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
             errs = []
             if job_action == "delete":
                 for job_id in job_ids:
-                    if not is_readable(job_id):
-                        errs.append(f"Job {job_id} is not readable.")
+                    if not (reason := is_readable(job_id)):
+                        errs.append(f"Job {job_id} is not readable: {_get_reason(reason)}.")
                         continue
-                    if not is_deletable(job_id):
-                        errs.append(f"Job {job_id} is not deletable.")
+                    if not (reason := is_deletable(job_id)):
+                        errs.append(f"Job {job_id} is not deletable: {_get_reason(reason)}.")
                         continue
                     try:
                         delete_job(core_get(job_id))
@@ -841,11 +843,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         errs.append(f"Error deleting job. {e}")
             elif job_action == "cancel":
                 for job_id in job_ids:
-                    if not is_readable(job_id):
-                        errs.append(f"Job {job_id} is not readable.")
+                    if not (reason := is_readable(job_id)):
+                        errs.append(f"Job {job_id} is not readable: {_get_reason(reason)}.")
                         continue
-                    if not is_editable(job_id):
-                        errs.append(f"Job {job_id} is not cancelable.")
+                    if not (reason := is_editable(job_id)):
+                        errs.append(f"Job {job_id} is not cancelable: {_get_reason(reason)}.")
                         continue
                     try:
                         cancel_job(job_id)
@@ -947,8 +949,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 job_id = e.get("job_id")
                 job: t.Optional[Job] = None
                 if job_id:
-                    if not is_readable(job_id):
-                        job_id += " not readable"
+                    if not (reason := is_readable(job_id)):
+                        job_id += f" is not readable: {_get_reason(reason)}."
                     else:
                         job = core_get(job_id)
                 res.append(
@@ -964,11 +966,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
         return _DoNotUpdate()
 
     def __check_readable_editable(self, state: State, id: str, ent_type: str, var: t.Optional[str]):
-        if not is_readable(t.cast(ScenarioId, id)):
-            _GuiCoreContext.__assign_var(state, var, f"{ent_type} {id} is not readable.")
+        if not (reason := is_readable(t.cast(ScenarioId, id))):
+            _GuiCoreContext.__assign_var(state, var, f"{ent_type} {id} is not readable: {_get_reason(reason)}.")
             return False
-        if not is_editable(t.cast(ScenarioId, id)):
-            _GuiCoreContext.__assign_var(state, var, f"{ent_type} {id} is not editable.")
+        if not (reason := is_editable(t.cast(ScenarioId, id))):
+            _GuiCoreContext.__assign_var(state, var, f"{ent_type} {id} is not editable: {_get_reason(reason)}.")
             return False
         return True
 
@@ -1001,7 +1003,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 _GuiCoreContext.__assign_var(state, error_var, f"Error updating Datanode value. {e}")
             _GuiCoreContext.__assign_var(state, payload.get("data_id"), entity_id)  # this will update the data value
 
-    def tabular_data_edit(self, state: State, var_name: str, payload: dict):
+    def tabular_data_edit(self, state: State, var_name: str, payload: dict):  # noqa:C901
         self.__lazy_start()
         error_var = payload.get("error_id")
         user_data = payload.get("user_data", {})
@@ -1028,6 +1030,23 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     elif isinstance(data, pd.Series):
                         data.at[idx] = val
                     new_data = data
+                elif isinstance(data, (dict, _MapDict)):
+                    row = data.get(col, None)
+                    data_tuple = False
+                    if isinstance(row, tuple):
+                        row = list(row)
+                        data_tuple = True
+                    if isinstance(row, list):
+                        row[idx] = val
+                        if data_tuple:
+                            data[col] = tuple(row)
+                        new_data = data
+                    else:
+                        _GuiCoreContext.__assign_var(
+                            state,
+                            error_var,
+                            "Error updating Datanode: dict values must be list or tuple.",
+                        )
                 else:
                     data_tuple = False
                     if isinstance(data, tuple):
@@ -1088,55 +1107,44 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def get_data_node_tabular_data(self, id: str):
         self.__lazy_start()
-        if (
-            id
-            and is_readable(t.cast(DataNodeId, id))
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                value = self.__read_tabular_data(dn)
-                if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
-                    return value
-            except Exception:
-                return None
+        if id and is_readable(t.cast(DataNodeId, id)) and (dn := core_get(id)) and isinstance(dn, DataNode):
+            if dn.is_ready_for_reading or (dn.edit_in_progress and dn.editor_id == self.gui._get_client_id()):
+                try:
+                    value = self.__read_tabular_data(dn)
+                    if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
+                        return value
+                except Exception:
+                    return None
         return None
 
     def get_data_node_tabular_columns(self, id: str):
         self.__lazy_start()
-        if (
-            id
-            and is_readable(t.cast(DataNodeId, id))
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                value = self.__read_tabular_data(dn)
-                if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
-                    return self.gui._tbl_cols(
-                        True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=value
-                    )
-            except Exception:
-                return None
+        if id and is_readable(t.cast(DataNodeId, id)) and (dn := core_get(id)) and isinstance(dn, DataNode):
+            if dn.is_ready_for_reading or (dn.edit_in_progress and dn.editor_id == self.gui._get_client_id()):
+                try:
+                    value = self.__read_tabular_data(dn)
+                    if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
+                        return self.gui._tbl_cols(
+                            True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=value
+                        )
+                except Exception:
+                    return None
         return None
 
     def get_data_node_chart_config(self, id: str):
         self.__lazy_start()
-        if (
-            id
-            and is_readable(t.cast(DataNodeId, id))
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                return self.gui._chart_conf(
-                    True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=self.__read_tabular_data(dn)
-                )
-            except Exception:
-                return None
+        if id and is_readable(t.cast(DataNodeId, id)) and (dn := core_get(id)) and isinstance(dn, DataNode):
+            if dn.is_ready_for_reading or (dn.edit_in_progress and dn.editor_id == self.gui._get_client_id()):
+                try:
+                    return self.gui._chart_conf(
+                        True,
+                        True,
+                        "{}",
+                        json.dumps({"data": "tabular_data"}),
+                        tabular_data=self.__read_tabular_data(dn),
+                    )
+                except Exception:
+                    return None
         return None
 
     def on_dag_select(self, state: State, id: str, payload: t.Dict[str, str]):
@@ -1147,7 +1155,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
         on_action_function = self.gui._get_user_function(args[1]) if args[1] else None
         if callable(on_action_function):
             try:
-                entity = core_get(args[0]) if is_readable(args[0]) else f"unredable({args[0]})"
+                entity = (
+                    core_get(args[0])
+                    if (reason := is_readable(args[0]))
+                    else f"{args[0]} is not readable: {_get_reason(reason)}"
+                )
                 self.gui._call_function_with_state(
                     on_action_function,
                     [entity],
@@ -1160,4 +1172,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def get_creation_reason(self):
         self.__lazy_start()
-        return "" if (reason := can_create()) else f"Cannot create scenario: {reason.reasons}"
+        return "" if (reason := can_create()) else f"Cannot create scenario: {_get_reason(reason)}"
+
+
+def _get_reason(reason: t.Union[bool, ReasonCollection]):
+    return reason.reasons if isinstance(reason, ReasonCollection) else " "

+ 2 - 2
tests/gui_core/test_context_is_deletable.py

@@ -91,7 +91,7 @@ class TestGuiCoreContext_is_deletable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not deletable.")
+                assert "is not deletable" in str(assign.call_args.args[1])
 
     def test_act_on_jobs(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
@@ -127,4 +127,4 @@ class TestGuiCoreContext_is_deletable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in str(assign.call_args.args[1])

+ 8 - 8
tests/gui_core/test_context_is_editable.py

@@ -88,7 +88,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in str(assign.call_args.args[1])
 
     def test_edit_entity(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -122,7 +122,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in str(assign.call_args.args[1])
 
     def test_act_on_jobs(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get), patch(
@@ -142,7 +142,7 @@ class TestGuiCoreContext_is_editable:
             )
             assign.assert_called_once()
             assert assign.call_args.args[0] == "error_var"
-            assert str(assign.call_args.args[1]).find("is not editable.") == -1
+            assert "is not editable" not in assign.call_args.args[1]
             assign.reset_mock()
 
             with patch("taipy.gui_core._context.is_readable", side_effect=mock_is_editable_false):
@@ -158,7 +158,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_edit_data_node(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -192,7 +192,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in assign.call_args.args[1]
 
     def test_lock_datanode_for_edit(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -228,7 +228,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in assign.call_args.args[1]
 
     def test_update_data(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -264,7 +264,7 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in assign.call_args.args[1]
 
     def test_tabular_data_edit(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -299,4 +299,4 @@ class TestGuiCoreContext_is_editable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not editable.")
+                assert "is not editable" in assign.call_args.args[1]

+ 2 - 2
tests/gui_core/test_context_is_promotable.py

@@ -65,7 +65,7 @@ class TestGuiCoreContext_is_promotable:
             )
             assign.assert_called_once()
             assert assign.call_args.args[0] == "error_var"
-            assert str(assign.call_args.args[1]).endswith("to primary because it doesn't belong to a cycle.")
+            assert "to primary because it doesn't belong to a cycle" in assign.call_args.args[1]
             assign.reset_mock()
 
             with patch("taipy.gui_core._context.is_promotable", side_effect=mock_is_promotable_false):
@@ -81,4 +81,4 @@ class TestGuiCoreContext_is_promotable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not promotable.")
+                assert "is not promotable" in assign.call_args.args[1]

+ 10 - 10
tests/gui_core/test_context_is_readable.py

@@ -138,7 +138,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_edit_entity(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -172,7 +172,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_submission_status_callback(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
@@ -239,7 +239,7 @@ class TestGuiCoreContext_is_readable:
             )
             assign.assert_called_once()
             assert assign.call_args.args[0] == "error_var"
-            assert str(assign.call_args.args[1]).find("is not readable.") == -1
+            assert "is not readable" not in assign.call_args.args[1]
             assign.reset_mock()
 
             gui_core_context.act_on_jobs(
@@ -254,7 +254,7 @@ class TestGuiCoreContext_is_readable:
             )
             assign.assert_called_once()
             assert assign.call_args.args[0] == "error_var"
-            assert str(assign.call_args.args[1]).find("is not readable.") == -1
+            assert "is not readable" not in assign.call_args.args[1]
             assign.reset_mock()
 
             with patch("taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
@@ -270,7 +270,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
                 assign.reset_mock()
 
                 gui_core_context.act_on_jobs(
@@ -285,7 +285,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_edit_data_node(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -319,7 +319,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_lock_datanode_for_edit(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -355,7 +355,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_get_scenarios_for_owner(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
@@ -402,7 +402,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_tabular_data_edit(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get):
@@ -437,7 +437,7 @@ class TestGuiCoreContext_is_readable:
                 )
                 assign.assert_called_once()
                 assert assign.call_args.args[0] == "error_var"
-                assert str(assign.call_args.args[1]).endswith("is not readable.")
+                assert "is not readable" in assign.call_args.args[1]
 
     def test_get_data_node_tabular_data(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget: