Skip to content

Commit

Permalink
GUI: Json Resolver (#826) (#864)
Browse files Browse the repository at this point in the history
* Json Resolver

* per Fabien and Fred

* fix linter
  • Loading branch information
dinhlongviolin1 authored Feb 23, 2024
1 parent 605ad9e commit 2ad5057
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 22 deletions.
1 change: 1 addition & 0 deletions taipy/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@

from ._init import *
from ._renderers import Html, Markdown
from ._renderers.json import JsonAdapter
from .gui_actions import (
broadcast_callback,
download,
Expand Down
66 changes: 44 additions & 22 deletions taipy/gui/_renderers/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,67 @@
# specific language governing permissions and limitations under the License.
from __future__ import annotations

import typing as t
from abc import ABC, abstractmethod
from datetime import date, datetime, time
from json import JSONEncoder
from pathlib import Path
from types import FunctionType, LambdaType

from flask.json.provider import DefaultJSONProvider

from .._warnings import _warn
from ..icon import Icon
from ..utils import _date_to_string, _MapDict, _TaipyBase
from ..utils.singleton import _Singleton


def _default(o):
if isinstance(o, Icon):
return o._to_dict()
if isinstance(o, _MapDict):
return o._dict
if isinstance(o, _TaipyBase):
return o.get()
if isinstance(o, (datetime, date, time)):
return _date_to_string(o)
if isinstance(o, Path):
return str(o)
if isinstance(o, FunctionType):
return o.__name__
if isinstance(o, LambdaType):
return str(o)
try:
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
except Exception as e:
_warn("Exception in JSONEncoder", e)
class JsonAdapter(ABC):
def register(self):
_TaipyJsonAdapter().register(self)

@abstractmethod
def parse(self, o) -> t.Union[t.Any, None]:
return None


class _DefaultJsonAdapter(JsonAdapter):
def parse(self, o):
if isinstance(o, Icon):
return o._to_dict()
if isinstance(o, _MapDict):
return o._dict
if isinstance(o, _TaipyBase):
return o.get()
if isinstance(o, (datetime, date, time)):
return _date_to_string(o)
if isinstance(o, Path):
return str(o)


class _TaipyJsonAdapter(object, metaclass=_Singleton):
def __init__(self):
self._adapters: t.List[JsonAdapter] = []
self.register(_DefaultJsonAdapter())

def register(self, adapter: JsonAdapter):
self._adapters.append(adapter)

def parse(self, o):
try:
for adapter in reversed(self._adapters):
if (output := adapter.parse(o)) is not None:
return output
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
except Exception as e:
_warn("Exception while resolving JSON", e)
return None


class _TaipyJsonEncoder(JSONEncoder):
def default(self, o):
return _default(o)
return _TaipyJsonAdapter().parse(o)


class _TaipyJsonProvider(DefaultJSONProvider):
default = staticmethod(_default) # type: ignore
default = staticmethod(_TaipyJsonAdapter().parse) # type: ignore
sort_keys = False

1 comment on commit 2ad5057

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Taipy Overall Coverage Report
FileStmtsMissCoverMissing
__init__.py16193%31
_entrypoint.py36488%44, 46, 50–51
_run.py38294%77–78
version.py90100% 
_cli
   __init__.py00100% 
   _help_cli.py190100% 
   _run_cli.py251156%40, 42–46, 48–49, 51, 53, 59
   _scaffold_cli.py18288%41–42
_cli/_base_cli
   __init__.py10100% 
   _cli.py410100% 
config
   __init__.py38976%41–42, 44–49, 70
   _config.py60198%68
   _init.py30100% 
   config.py145497%87, 154, 178, 233
   section.py42685%35, 40, 44, 48, 53, 57
   setup.py15150%14–16, 18, 20–21, 23, 25–29, 31, 33, 35
   unique_section.py50100% 
   version.py90100% 
config/_config_comparator
   __init__.py00100% 
   _comparator_result.py95396%67, 110–111
   _config_comparator.py680100% 
config/_serializer
   __init__.py00100% 
   _base_serializer.py124595%39, 141–142, 144–145
   _json_serializer.py24387%33–35
   _toml_serializer.py230100% 
config/checker
   __init__.py00100% 
   _checker.py140100% 
   issue.py150100% 
   issue_collector.py280100% 
config/checker/_checkers
   __init__.py00100% 
   _auth_config_checker.py25250%12–14, 17–19, 21–24, 26–30, 32–34, 39–40, 47–49, 54–55
   _config_checker.py30196%28
config/common
   __init__.py00100% 
   _classproperty.py50100% 
   _config_blocker.py240100% 
   _repr_enum.py70100% 
   _template_handler.py1111190%39, 53, 55, 59, 61, 63, 99–100, 118, 149–150
   _validate_id.py100100% 
   frequency.py70100% 
   scope.py220100% 
   typing.py30100% 
config/exceptions
   __init__.py10100% 
   exceptions.py50100% 
config/global_app
   __init__.py00100% 
   global_app_config.py32196%38
core
   __init__.py70100% 
   _core.py560100% 
   _core_cli.py460100% 
   _init.py180100% 
   _init_version.py100100% 
   setup.py16160%14–16, 18, 20–21, 23–28, 30, 39, 41, 50
   taipy.py3904588%163, 219, 262, 267, 272, 277, 282, 287, 292, 297, 302, 340–342, 347, 352, 357, 362, 367, 372, 377, 382, 422, 461, 490–491, 498–504, 530–533, 941, 980, 996, 1045–1049
core/_backup
   __init__.py00100% 
   _backup.py33293%54–55
core/_entity
   __init__.py00100% 
   _dag.py65198%94
   _entity.py220100% 
   _entity_ids.py220100% 
   _labeled.py480100% 
   _migrate_cli.py74494%98–99, 117–118
   _properties.py350100% 
   _reload.py62198%72
   submittable.py75494%47, 102, 106, 110
core/_entity/_migrate
   __init__.py30100% 
   _migrate_fs.py66592%43, 68, 104–106
   _migrate_mongo.py76593%52, 71, 87, 105, 127
   _migrate_sql.py1065350%30–31, 33–36, 38–46, 53, 57, 64–65, 69, 76–77, 81, 89–90, 94, 100–101, 105, 110–111, 115, 120–121, 125, 127–137, 139–140, 154, 198–199, 201–202
   _utils.py1993084%32, 34–36, 38, 59–61, 63–67, 69–71, 73–74, 157, 160, 169, 197–199, 202–204, 241–242, 316
core/_manager
   __init__.py00100% 
   _manager.py81198%113
   _manager_factory.py21290%28, 32
core/_orchestrator
   __init__.py00100% 
   _abstract_orchestrator.py23482%27, 40, 53, 58
   _orchestrator.py1891492%146, 190–191, 193–199, 201–203, 291
   _orchestrator_factory.py641182%38, 58, 73–74, 76, 79, 89, 94, 97–98, 100
core/_orchestrator/_dispatcher
   __init__.py30100% 
   _development_job_dispatcher.py19478%27, 30, 33, 36
   _job_dispatcher.py96891%70, 77–79, 102–103, 120, 138
   _standalone_job_dispatcher.py310100% 
   _task_function_wrapper.py57394%75–76, 84
core/_repository
   __init__.py00100% 
   _abstract_converter.py10280%19, 24
   _abstract_repository.py361072%29, 41, 53, 63, 73, 80, 90, 101, 113, 124
   _base_taipy_model.py31487%27–28, 50, 53
   _decoder.py21290%36, 51
   _encoder.py19384%30, 36, 41
   _filesystem_repository.py1592186%147, 173–174, 176, 178–181, 190–193, 197–200, 224, 237–238, 257–258
   _sql_repository.py1311687%79–80, 123, 137, 143–144, 147–148, 153–157, 163, 180, 216
core/_repository/db
   __init__.py00100% 
   _sql_base_model.py30100% 
   _sql_connection.py46295%82–83
core/_version
   __init__.py00100% 
   _utils.py260100% 
   _version.py140100% 
   _version_converter.py140100% 
   _version_fs_repository.py68592%44–45, 100–101, 134
   _version_manager.py149894%98, 104, 148–149, 171, 212, 220, 230
   _version_manager_factory.py19289%26, 29
   _version_mixin.py190100% 
   _version_model.py210100% 
   _version_repository_interface.py26773%22, 26, 30, 34, 38, 42, 46
   _version_sql_repository.py530100% 
core/_version/_cli
   __init__.py00100% 
   _bcolor.py241058%21, 23–25, 27–31, 33
   _version_cli.py138497%81, 85, 193, 195
core/common
   __init__.py10100% 
   _check_dependencies.py5180%28
   _check_instance.py230100% 
   _listattributes.py29389%21–22, 32
   _mongo_connector.py17288%37, 45
   _repr_enum.py70100% 
   _utils.py390100% 
   _warnings.py210100% 
   mongo_default_document.py50100% 
   typing.py70100% 
   warn_if_inputs_not_ready.py140100% 
core/config
   __init__.py300100% 
   core_section.py197597%139, 148, 157, 166, 179
   data_node_config.py350798%287, 291, 637, 639, 643, 645, 1044
   job_config.py73395%125–127
   migration_config.py46197%57
   scenario_config.py149497%211, 215, 223, 227
   task_config.py97792%70–71, 149, 152, 220–222
core/config/checkers
   __init__.py00100% 
   _config_id_checker.py180100% 
   _core_section_checker.py180100% 
   _data_node_config_checker.py91297%112, 147
   _job_config_checker.py190100% 
   _migration_config_checker.py300100% 
   _scenario_config_checker.py610100% 
   _task_config_checker.py340100% 
core/cycle
   __init__.py00100% 
   _cycle_converter.py110100% 
   _cycle_fs_repository.py60100% 
   _cycle_manager.py1010100% 
   _cycle_manager_factory.py20290%28, 31
   _cycle_model.py230100% 
   _cycle_sql_repository.py60100% 
   cycle.py109298%81, 147
   cycle_id.py30100% 
core/data
   __init__.py130100% 
   _abstract_file.py170100% 
   _abstract_sql.py1833083%95, 143, 145, 149, 162–163, 166–167, 179, 185, 187, 189, 195, 202, 235, 254–261, 268, 274, 290, 299–301, 307
   _abstract_tabular.py45197%40
   _data_converter.py175597%114, 183, 225, 258, 267
   _data_fs_repository.py60100% 
   _data_manager.py117298%93, 179
   _data_manager_factory.py20290%28, 31
   _data_model.py320100% 
   _data_sql_repository.py60100% 
   _filter.py1931791%41, 63, 83–91, 143, 183, 190, 205, 213, 222
   aws_s3.py40295%103, 106
   csv.py109397%91, 205, 209
   data_node.py293797%158, 226, 278, 287, 464, 467, 471
   data_node_id.py50100% 
   excel.py2022886%95, 279, 282, 284–288, 292–293, 296, 298–300, 302, 304–305, 307, 312, 314–315, 317–320, 322, 343, 365
   generic.py48197%83
   in_memory.py280100% 
   json.py115595%91, 170, 174, 179, 183
   mongo.py1131289%207–214, 222, 232, 237, 289
   operator.py110100% 
   parquet.py114397%107, 122, 230
   pickle.py630100% 
   sql.py44197%100
   sql_table.py660100% 
core/exceptions
   __init__.py10100% 
   exceptions.py118595%183, 191, 229, 267, 309
core/job
   __init__.py00100% 
   _job_converter.py31293%59–60
   _job_fs_repository.py60100% 
   _job_manager.py570100% 
   _job_manager_factory.py20290%28, 31
   _job_model.py260100% 
   _job_sql_repository.py60100% 
   job.py187597%30, 77, 149, 295, 336
   job_id.py30100% 
   status.py110100% 
core/notification
   __init__.py70100% 
   _registration.py180100% 
   _topic.py23195%65
   core_event_consumer.py26196%84
   event.py40197%139
   notifier.py470100% 
   registration_id.py30100% 
core/scenario
   __init__.py00100% 
   _scenario_converter.py320100% 
   _scenario_fs_repository.py60100% 
   _scenario_manager.py273996%88–91, 183, 188, 384, 429, 440
   _scenario_manager_factory.py20290%28, 31
   _scenario_model.py300100% 
   _scenario_sql_repository.py60100% 
   scenario.py3191495%129, 132, 134–135, 177, 224, 243, 324, 366, 409, 601, 603, 659, 665
   scenario_id.py30100% 
core/sequence
   __init__.py00100% 
   _sequence_converter.py190100% 
   _sequence_manager.py2381394%158, 227, 241, 243, 246, 251, 257, 283–286, 353, 371
   _sequence_manager_factory.py11190%23
   sequence.py144397%81, 160, 172
   sequence_id.py30100% 
core/submission
   __init__.py00100% 
   _submission_converter.py200100% 
   _submission_fs_repository.py60100% 
   _submission_manager.py600100% 
   _submission_manager_factory.py20290%28, 31
   _submission_model.py310100% 
   _submission_sql_repository.py60100% 
   submission.py171795%111, 119, 140, 186, 189, 195, 248
   submission_id.py30100% 
   submission_status.py100100% 
core/task
   __init__.py00100% 
   _task_converter.py28196%68
   _task_fs_repository.py60100% 
   _task_manager.py121595%153–154, 159–160, 200
   _task_manager_factory.py20290%28, 31
   _task_model.py250100% 
   _task_sql_repository.py60100% 
   task.py107595%91, 96, 167–169
   task_id.py30100% 
gui
   __init__.py160100% 
   _default_config.py30100% 
   _gui_cli.py370100% 
   _gui_section.py300100% 
   _init.py10100% 
   _page.py32293%20–21
   _warnings.py110100% 
   config.py1101685%175, 177, 179–180, 184, 193, 201, 203, 207, 209, 211, 275–276, 281–282, 323
   gui.py125424580%41, 362, 364, 410–416, 419–426, 428–429, 431, 433–434, 436–438, 440–441, 443, 445–447, 449–452, 454–455, 457–462, 516, 527, 529, 531, 551, 574, 576, 589–590, 596–597, 618–623, 637, 641, 653, 667, 672, 679–680, 704–706, 716, 745, 754, 774, 791–792, 796–802, 804–809, 842–843, 857, 866, 954, 956, 961–962, 965, 968–969, 971, 973, 977, 979–980, 985, 987, 992, 998–1005, 1007, 1009, 1022–1026, 1028–1031, 1045–1047, 1054, 1056–1057, 1060, 1069–1078, 1083, 1087–1088, 1096, 1101, 1112–1119, 1151–1153, 1220–1221, 1286, 1288, 1293, 1302–1303, 1314, 1324, 1330, 1358, 1360–1361, 1414, 1504–1506, 1516, 1525–1533, 1557, 1563, 1719–1722, 1768, 1770, 1821, 1837, 1851–1853, 1855, 1858–1859, 1871, 1876–1878, 1880, 1938, 1952–1953, 1982, 1987, 1999–2002, 2008, 2020–2025, 2045, 2052–2054, 2069, 2082, 2094, 2106, 2108, 2112–2113, 2121–2123, 2137, 2253, 2385, 2398, 2408, 2412
   gui_actions.py962771%61, 101, 133, 148, 174, 196–199, 220, 234, 238–239, 261–263, 289, 312–314, 318–319, 371, 380, 382, 387, 393
   gui_types.py92693%148, 153, 158, 165, 176, 178
   icon.py231630%58–63, 66–75
   page.py511864%21, 46, 48, 50, 55–57, 62–67, 98, 109, 112–114
   partial.py27388%21, 66, 73
   server.py1723778%42, 67, 91, 109, 152–158, 160–161, 174–175, 184, 187, 194, 205, 256, 258–259, 282, 291, 293, 298–299, 306–307, 309–311, 321, 323, 325, 328–329
   setup.py26260%14, 16–18, 20–21, 23, 25–30, 32, 51, 53, 63–67, 70–73, 76
   state.py1081883%24, 128, 140, 154, 180, 195, 210–212, 222–223, 235–238, 241–242, 245
gui/_renderers
   __init__.py731086%21–22, 40, 49, 60, 85–87, 91, 98
   builder.py66411382%45, 124, 143–144, 160–162, 164, 177, 204, 236–237, 241, 259–260, 264, 279, 297, 302–303, 307, 318, 335–338, 347, 350–352, 365, 378, 380–381, 384, 388–391, 393, 400, 442, 454, 500–503, 505–509, 511–514, 516–520, 530, 550, 567, 574, 579, 594, 601, 604–605, 607, 610, 614, 629, 636, 662, 674, 676–680, 683–684, 708, 711, 713, 799, 802, 825–826, 830, 862, 867–870, 872–875, 881–882, 892, 945, 962, 967, 974–978, 1001
   factory.py78396%20–21, 578
   json.py50982%29, 33, 39, 46–47, 63–66
   utils.py66887%38–39, 54, 66–67, 78–79, 90
gui/_renderers/_html
   __init__.py10100% 
   factory.py9188%22
   parser.py981386%41–42, 52–53, 63, 70, 74, 82–83, 108–109, 121, 134
gui/_renderers/_markdown
   __init__.py18194%30
   blocproc.py35488%48–49, 67–68
   control.py110100% 
   factory.py140100% 
   postproc.py190100% 
   preproc.py122695%23, 103, 132, 136, 140, 199
gui/builder
   __init__.py40100% 
   _api_generator.py59689%25, 43, 45, 51, 70, 73
   _context_manager.py13192%17
   _element.py951089%24, 49–50, 65, 73, 78, 160, 171, 189, 192
   _factory.py9188%22
   page.py30970%57, 61–66, 70, 72
gui/custom
   __init__.py10100% 
   _page.py301260%28–32, 45, 48, 52, 63, 70, 79, 86
gui/data
   __init__.py30100% 
   array_dict_data_accessor.py40685%47–48, 50–51, 59, 66
   content_accessor.py961881%51, 58–60, 64, 66, 73, 85–86, 91–92, 109–110, 124–126, 128, 131
   data_accessor.py721579%27, 33, 37, 43, 48, 51, 72, 74, 77, 83, 87–88, 97–99
   data_format.py40100% 
   data_scope.py37294%61–62
   numpy_data_accessor.py21766%30, 33–35, 40–42
   pandas_data_accessor.py2406572%81, 83–92, 94, 96–103, 109, 117, 149–151, 156–159, 182, 204, 207–212, 231, 262–263, 279–280, 283, 292–294, 300–301, 311, 317–319, 353–357, 359, 364–365, 374–376, 406, 409
   utils.py592852%22, 53–54, 57–59, 82, 98, 118–119, 121–125, 127–133, 136, 138, 141–144
gui/data/decimator
   __init__.py40100% 
   lttb.py38294%53, 56
   minmax.py25196%45
   rdp.py64592%85, 112–114, 140
   scatter_decimator.py501374%62–63, 77–83, 87–90
gui/extension
   __init__.py20100% 
   library.py1391887%27–28, 62, 71, 75, 81, 132, 141, 143, 176, 240, 270, 343, 349, 351, 364, 412, 422
gui/utils
   __init__.py220100% 
   _adapter.py1175453%39–40, 44–45, 48–55, 58–70, 82, 87, 92, 98–100, 103–108, 116, 126–133, 141–145, 149, 151, 153
   _attributes.py30776%16, 31–32, 39–40, 56–57
   _bindings.py50198%20
   _evaluator.py2284281%22, 124–126, 149, 151, 161, 169–170, 199–201, 244–248, 250–260, 270, 290–293, 295–298, 301, 313, 326–327, 338
   _locals_context.py48295%54, 60
   _map_dict.py66592%34, 55, 70–71, 73
   _runtime_manager.py13284%17, 30
   _variable_directory.py100892%47–50, 61, 72, 84, 88
   boolean.py9188%27
   chart_config_builder.py1381291%23, 91–92, 107, 154–155, 195, 198–200, 266, 268
   clientvarname.py14285%23, 26
   datatype.py11372%20, 22, 24
   date.py17382%27, 30–31
   expr_var_name.py13192%23
   filename.py120100% 
   filter_locals.py50100% 
   get_imported_var.py110100% 
   get_module_name.py130100% 
   get_page_from_module.py5180%19
   getdatecolstrname.py8187%21
   html.py9188%22
   is_debugging.py3166%17
   is_port_open.py60100% 
   isnotebook.py10100% 
   proxy.py65650%12–17, 19–22, 24, 27, 29, 36–37, 40–46, 49, 52–53, 55–60, 62–63, 70–71, 73–79, 87–88, 91–95, 97–103, 106–109, 111–115
   singleton.py70100% 
   table_col_builder.py481960%21, 29, 38–40, 42, 52–54, 56, 59–61, 63, 66–68, 70, 116
   types.py1493179%49–50, 104–110, 138–141, 144–146, 185–186, 195–199, 201–208
   varnamefromcontent.py6183%17
gui_core
   _GuiCoreLib.py340100% 
   __init__.py10100% 
   _adapters.py823853%59–64, 100–101, 103, 113, 116–129, 133, 146–147, 149, 166–172, 196–197, 199
   _context.py60024359%21–22, 163, 172–173, 199, 203–204, 217, 232–233, 242–248, 250–251, 254–257, 264–265, 268–273, 285, 312–316, 319–325, 327–336, 351, 353–356, 358–359, 368–372, 374, 379, 381–384, 386, 389, 392, 396–407, 412, 423, 435–436, 438–443, 445, 449, 458, 464, 480–487, 492–495, 498–500, 510–514, 521–522, 529–531, 538–539, 547–550, 570–571, 577, 589–590, 601–602, 612, 622–623, 628, 640, 642–643, 650, 656, 659–662, 665–668, 677–679, 681, 685, 691, 698–704, 706–707, 716–717, 722, 725, 728, 735–742, 749–752, 758–761, 775, 795–796, 819–823, 827–828, 830–842, 844, 848–849, 856–859, 878–880, 896, 899–900, 917–918, 922–933
   _init.py50100% 
logger
   __init__.py00100% 
   _taipy_logger.py22290%30–31
rest
   __init__.py40100% 
   _init.py10100% 
   app.py200100% 
   extensions.py30100% 
   rest.py7185%45
   setup.py12120%11–12, 14, 16–17, 19–24, 26
   version.py90100% 
rest/api
   __init__.py20100% 
   error_handler.py49197%88
   views.py64493%165–166, 212–213
rest/api/exceptions
   __init__.py00100% 
   exceptions.py90100% 
rest/api/middlewares
   __init__.py00100% 
   _middleware.py14192%34
rest/api/resources
   __init__.py70100% 
   cycle.py490100% 
   datanode.py87396%465, 584, 603
   job.py70494%201–204
   scenario.py650100% 
   sequence.py66198%293
   task.py65198%202
rest/api/schemas
   __init__.py70100% 
   cycle.py100100% 
   datanode.py630100% 
   job.py120100% 
   scenario.py120100% 
   sequence.py100100% 
   task.py110100% 
rest/commons
   __init__.py00100% 
   apispec.py51982%35, 49, 84, 87, 91, 98–100, 103
   encoder.py13653%22–25, 27–28
   pagination.py14140%12, 14, 16–17, 20–23, 26–29, 36, 44
   to_from_model.py80100% 
templates/default/{{cookiecutter.__root_folder_name}}
   requirements.txt10100% 
   {{cookiecutter.__main_file}}.py00100% 
templates/default/{{cookiecutter.__root_folder_name}}/algorithms
   __init__.py110%12
   algorithms.py10100% 
templates/default/{{cookiecutter.__root_folder_name}}/configuration
   __init__.py110%12
   config.py3233%18, 20
templates/default/{{cookiecutter.__root_folder_name}}/pages
   __init__.py110%12
   root.py3233%19, 21
templates/default/{{cookiecutter.__root_folder_name}}/pages/page_example
   page_example.md00100% 
   page_example.py3233%19, 21
templates/default/{{cookiecutter.__root_folder_name}}/sections
   import.txt10100% 
   page_content.txt00100% 
templates/scenario-management/{{cookiecutter.__root_folder_name}}
   .taipyignore00100% 
templates/scenario-management/{{cookiecutter.__root_folder_name}}/algos
   __init__.py110%12
   algos.py3166%15
templates/scenario-management/{{cookiecutter.__root_folder_name}}/config
   __init__.py00100% 
   config.py9722%17, 22–25, 31, 34
   config.toml18950%3, 15–17, 19–22, 24
   config_with_toml.py440%12, 15–17
templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page
   __init__.py110%12
   job_page.py2150%12
templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page
   __init__.py110%12
   data_node_management.py141214%20–21, 26, 34–35, 42–43, 46–50
   scenario_page.py11109%12, 17–21, 23, 26–27, 30
TOTAL17671211288% 

Please sign in to comment.