hap-cli 0.8.8__tar.gz → 0.8.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hap_cli-0.8.8 → hap_cli-0.8.9}/PKG-INFO +1 -1
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/chart_cmd.py +7 -5
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/chatbot_cmd.py +2 -1
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/record_cmd.py +6 -3
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/chart.py +19 -1
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/chatbot.py +46 -9
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/record.py +90 -14
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_chart.py +78 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_core.py +148 -14
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.8 → hap_cli-0.8.9}/MANIFEST.in +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/README.md +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/README.md +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/context.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/SOURCES.txt +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/charts.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/config.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/errors.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/executor.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/fields.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/hap.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/schema.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/step.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/steps.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/store.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/setup.cfg +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/setup.py +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.8 → hap_cli-0.8.9}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -22,12 +22,14 @@ def chart():
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
# Report-type enum values used by the MingDAO statistics module. The CLI
|
|
25
|
-
# accepts the integer directly so users can pass uncommon types
|
|
26
|
-
#
|
|
25
|
+
# accepts the integer directly so users can pass uncommon types without us
|
|
26
|
+
# having to enumerate every one. Note: there is NO type 4 — passing 4 is
|
|
27
|
+
# coerced to 10 (number) by the server, so it is omitted here (bug #036584).
|
|
27
28
|
_REPORT_TYPE_HINT = (
|
|
28
|
-
"Chart type (integer).
|
|
29
|
-
"1=column, 2=line, 3=pie,
|
|
30
|
-
"
|
|
29
|
+
"Chart type (integer). Values: "
|
|
30
|
+
"1=column, 2=line, 3=pie, 5=radar, 6=funnel, 7=dual-axis, 8=pivot, "
|
|
31
|
+
"9=region-map, 10=number, 11=bidirectional-bar, 12=scatter, "
|
|
32
|
+
"13=word-cloud, 14=gauge, 15=progress, 16=ranking, 17=world-map."
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
|
|
@@ -120,7 +120,8 @@ def chatbot_rename(ctx, app_id, chatbot_id, section_id, name, icon):
|
|
|
120
120
|
@click.option("--app-id", "-a", required=True, help="Application ID")
|
|
121
121
|
@org_id_option()
|
|
122
122
|
@click.option("--section-id", default="",
|
|
123
|
-
help="Section/group the assistant lives under.
|
|
123
|
+
help="Section/group the assistant lives under. Auto-detected "
|
|
124
|
+
"when omitted.")
|
|
124
125
|
@click.option("--permanent", is_flag=True, default=False,
|
|
125
126
|
help="Delete permanently instead of moving to the recycle bin.")
|
|
126
127
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
@@ -156,9 +156,12 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow):
|
|
|
156
156
|
Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
|
|
157
157
|
--fields-json array.
|
|
158
158
|
|
|
159
|
-
A sub-table value is
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
A sub-table value is a list of child rows, each a dict keyed by child
|
|
160
|
+
field name. Updates are incremental: a child row with a "rowid" key
|
|
161
|
+
edits that existing row in place (its id is kept and only the listed
|
|
162
|
+
fields change), a child row without a "rowid" adds a new row, and a
|
|
163
|
+
child row with "_delete": true removes that row. Rows you don't list
|
|
164
|
+
are left untouched. An empty list (or null) clears the sub-table.
|
|
162
165
|
"""
|
|
163
166
|
if field and fields_json:
|
|
164
167
|
raise click.UsageError("Pass exactly one of -f or --fields-json.")
|
|
@@ -17,7 +17,14 @@ Save body shape (verified against pd-openweb @param docs):
|
|
|
17
17
|
- ``name`` — chart display name
|
|
18
18
|
- ``appId`` — worksheet id the chart attaches to
|
|
19
19
|
- ``appType`` — 1: worksheet, 2: aggregate table (default 1)
|
|
20
|
-
- ``reportType`` —
|
|
20
|
+
- ``reportType`` — chart type. The statistics module's canonical
|
|
21
|
+
enum (``pd-openweb/src/pages/Statistics/Charts/common.js`` →
|
|
22
|
+
``reportTypes``) is: 1:柱图 2:折线图 3:饼图 5:雷达图 6:漏斗图
|
|
23
|
+
7:双轴图 8:透视表 9:行政区划 10:数值图 11:双向条形图 12:散点图
|
|
24
|
+
13:词云 14:仪表图 15:进度图 16:数值排名 17:世界地图. There is
|
|
25
|
+
**no reportType 4** — an old API doc comment labels 4 as 数值图,
|
|
26
|
+
but the live backend coerces a sent ``4`` to ``10`` (the real
|
|
27
|
+
数值图). Pass 10 for a number chart, never 4 (bug #036584).
|
|
21
28
|
- ``filter`` / ``filters`` / ``config`` / ``displaySetup`` / ``pivotTable``
|
|
22
29
|
/ ``summary`` / ``xaxes`` / ``yaxisList`` / ``sorts`` / ``formulas`` —
|
|
23
30
|
optional nested config objects; CLI accepts a full ``--spec-json`` so the
|
|
@@ -200,6 +207,17 @@ def save_chart(
|
|
|
200
207
|
charts) are needed for that path.
|
|
201
208
|
"""
|
|
202
209
|
body: dict[str, Any] = dict(extra or {})
|
|
210
|
+
# The statistics backend reads the split (拆分维度/分组) control from
|
|
211
|
+
# the TOP-LEVEL `splitId` field, not from the nested `split.controlId`.
|
|
212
|
+
# A spec that only sets `split.controlId` (the natural shape, and what
|
|
213
|
+
# the UI's drag-into-split slot produces internally) gets its split
|
|
214
|
+
# silently cleared on save unless `splitId` mirrors it. Auto-populate
|
|
215
|
+
# `splitId` from `split.controlId` when the caller hasn't pinned one
|
|
216
|
+
# (bug #036584). An explicit splitId always wins.
|
|
217
|
+
if not body.get("splitId") and isinstance(body.get("split"), dict):
|
|
218
|
+
split_control_id = body["split"].get("controlId")
|
|
219
|
+
if split_control_id:
|
|
220
|
+
body["splitId"] = split_control_id
|
|
203
221
|
# Resolve the chart's filter block before sending saveReportConfig.
|
|
204
222
|
if isinstance(body.get("filter"), dict):
|
|
205
223
|
body["filter"] = _ensure_filter_id(
|
|
@@ -196,6 +196,37 @@ def update_chatbot_info(
|
|
|
196
196
|
return session.api_call("AppManagement", "EditWorkSheetInfoForApp", data)
|
|
197
197
|
|
|
198
198
|
|
|
199
|
+
def _resolve_chatbot_section(
|
|
200
|
+
session: Session, app_id: str, chatbot_id: str
|
|
201
|
+
) -> str:
|
|
202
|
+
"""Find the appSectionId of the section holding ``chatbot_id``.
|
|
203
|
+
|
|
204
|
+
Walks the app's sections (and nested childSections) for the item whose
|
|
205
|
+
``workSheetId`` is the chatbot, returning its section id. Empty string
|
|
206
|
+
if not found (caller proceeds without it).
|
|
207
|
+
"""
|
|
208
|
+
from hap_cli.core.app import get_app_detail
|
|
209
|
+
|
|
210
|
+
detail = get_app_detail(session, app_id)
|
|
211
|
+
|
|
212
|
+
def _walk(sections: list[dict[str, Any]]) -> str:
|
|
213
|
+
for sec in sections or []:
|
|
214
|
+
for item in sec.get("workSheetInfo", []) or []:
|
|
215
|
+
if item.get("workSheetId") == chatbot_id:
|
|
216
|
+
return sec.get("appSectionId", "") or ""
|
|
217
|
+
found = _walk(sec.get("childSections") or [])
|
|
218
|
+
if found:
|
|
219
|
+
return found
|
|
220
|
+
return ""
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
return _walk(detail.get("sections", []) or [])
|
|
224
|
+
except Exception as e: # never block a delete on a lookup hiccup
|
|
225
|
+
logger.warning("could not resolve chatbot %s section: %s",
|
|
226
|
+
chatbot_id, e)
|
|
227
|
+
return ""
|
|
228
|
+
|
|
229
|
+
|
|
199
230
|
def delete_chatbot(
|
|
200
231
|
session: Session,
|
|
201
232
|
chatbot_id: str,
|
|
@@ -207,17 +238,23 @@ def delete_chatbot(
|
|
|
207
238
|
) -> dict[str, Any]:
|
|
208
239
|
"""Delete a chatbot (AI assistant).
|
|
209
240
|
|
|
210
|
-
Evidence: pd-openweb ``src/pages/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
flow with ``type: 1``
|
|
214
|
-
``workSheetId`` = the chatbot
|
|
215
|
-
page (see
|
|
216
|
-
sends it to
|
|
241
|
+
Evidence: pd-openweb ``src/pages/Chatbot/MoreMenu.jsx`` ->
|
|
242
|
+
``sheetList.deleteSheet`` -> ``RemoveWorkSheetForApp``. Even though a
|
|
243
|
+
chatbot is *created* as a ``type:3`` worksheet (``AddWorkSheet``), it
|
|
244
|
+
is *deleted* through the shared deleteSheet flow with ``type: 1``
|
|
245
|
+
(``AppManagement/RemoveWorkSheetForApp``, ``workSheetId`` = the chatbot
|
|
246
|
+
id) — the same call shape as a custom page (see
|
|
247
|
+
``core/page.delete_page``). ``isPermanentlyDelete=False`` sends it to
|
|
248
|
+
the recycle bin.
|
|
217
249
|
|
|
218
|
-
``appSectionId``
|
|
219
|
-
|
|
250
|
+
``appSectionId`` is REQUIRED in practice: the UI always sends
|
|
251
|
+
``parentGroupId || groupId`` and the server returns ``服务异常`` without
|
|
252
|
+
it. When the caller omits ``section_id`` we resolve the chatbot's
|
|
253
|
+
section from the app structure so a bare ``chatbot delete <id> -a
|
|
254
|
+
<app>`` just works.
|
|
220
255
|
"""
|
|
256
|
+
if not section_id:
|
|
257
|
+
section_id = _resolve_chatbot_section(session, app_id, chatbot_id)
|
|
221
258
|
data: dict[str, Any] = {
|
|
222
259
|
"appId": app_id,
|
|
223
260
|
"projectId": project_id,
|
|
@@ -76,12 +76,54 @@ def _sublist_parse_rows(ctrl: dict[str, Any], value: Any) -> list:
|
|
|
76
76
|
return list(value)
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
# Reserved keys a child-row dict may carry that are NOT child fields.
|
|
80
|
+
# ``rowid`` (or ``_rowid``) pins an existing child row so an update edits
|
|
81
|
+
# it in place and preserves its id; ``_delete`` (or ``_deleted``) marks
|
|
82
|
+
# that row for removal. They never map to a cell.
|
|
83
|
+
_SUBLIST_ROWID_KEYS = ("rowid", "_rowid")
|
|
84
|
+
_SUBLIST_DELETE_KEYS = ("_delete", "_deleted")
|
|
85
|
+
_SUBLIST_RESERVED_KEYS = frozenset(_SUBLIST_ROWID_KEYS + _SUBLIST_DELETE_KEYS)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _sublist_child_rowid(row: dict[str, Any]) -> Optional[str]:
|
|
89
|
+
"""Return the existing child-row id a row dict pins, or None.
|
|
90
|
+
|
|
91
|
+
A ``temp-``/``default-`` prefixed value names a row that does not yet
|
|
92
|
+
exist server-side, so it is treated as "no id" (a fresh add).
|
|
93
|
+
"""
|
|
94
|
+
for k in _SUBLIST_ROWID_KEYS:
|
|
95
|
+
v = (row or {}).get(k)
|
|
96
|
+
if v not in (None, ""):
|
|
97
|
+
s = str(v)
|
|
98
|
+
if s.startswith("temp-") or s.startswith("default-"):
|
|
99
|
+
return None
|
|
100
|
+
return s
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _sublist_row_is_delete(row: dict[str, Any]) -> bool:
|
|
105
|
+
return any((row or {}).get(k) for k in _SUBLIST_DELETE_KEYS)
|
|
106
|
+
|
|
107
|
+
|
|
79
108
|
def _sublist_row_cells(
|
|
80
109
|
ctrl: dict[str, Any], row: dict[str, Any],
|
|
110
|
+
*, temp_row_id: Optional[str] = None,
|
|
81
111
|
) -> list[dict[str, Any]]:
|
|
82
112
|
"""Serialize one child-row dict (keyed by child field name/alias/id)
|
|
83
113
|
into the ``[{controlId, value}, ...]`` cell list the server expects,
|
|
84
|
-
prefixed with a
|
|
114
|
+
prefixed with a ``tempRowId`` cell.
|
|
115
|
+
|
|
116
|
+
For a brand-new row ``temp_row_id`` is left None and a fresh
|
|
117
|
+
``temp-<uuid>`` id is minted. For an in-place update of an existing
|
|
118
|
+
row, pass that row's real id as ``temp_row_id`` — the UI sends the
|
|
119
|
+
existing rowid in the ``tempRowId`` cell on edit (evidence: a
|
|
120
|
+
captured UpdateWorksheetRow body where an in-place child edit carried
|
|
121
|
+
``{"controlId":"tempRowId","value":"<existing rowid>"}``).
|
|
122
|
+
|
|
123
|
+
Reserved keys (``rowid`` / ``_delete`` …) are skipped — they steer
|
|
124
|
+
the op, they are not child-field cells. Only the keys the caller
|
|
125
|
+
supplies become cells, so an in-place update touches just the
|
|
126
|
+
changed fields.
|
|
85
127
|
"""
|
|
86
128
|
child_by_name: dict[str, dict[str, Any]] = {}
|
|
87
129
|
for cc in ctrl.get("relationControls") or []:
|
|
@@ -91,9 +133,11 @@ def _sublist_row_cells(
|
|
|
91
133
|
child_by_name[k] = cc
|
|
92
134
|
cells = [{
|
|
93
135
|
"controlId": "tempRowId",
|
|
94
|
-
"value": "temp-" + str(uuid.uuid4()),
|
|
136
|
+
"value": temp_row_id or ("temp-" + str(uuid.uuid4())),
|
|
95
137
|
}]
|
|
96
138
|
for cname, cval in (row or {}).items():
|
|
139
|
+
if cname in _SUBLIST_RESERVED_KEYS:
|
|
140
|
+
continue
|
|
97
141
|
cc = child_by_name.get(cname)
|
|
98
142
|
if cc is None or cc.get("type") in _LEGACY_SKIP_TYPES:
|
|
99
143
|
continue
|
|
@@ -109,20 +153,51 @@ def _sublist_update_value(ctrl: dict[str, Any], value: Any) -> str:
|
|
|
109
153
|
|
|
110
154
|
Updates must NOT reuse the AddWorksheetRow 2-D array — the server
|
|
111
155
|
answers "服务异常". The UI sends ``editType: 9`` on the control and a
|
|
112
|
-
value of per-row operations
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
156
|
+
value of per-row operations. We apply **incremental / patch**
|
|
157
|
+
semantics, matching what the form actually sends (it only emits the
|
|
158
|
+
rows that changed and leaves the rest untouched):
|
|
159
|
+
|
|
160
|
+
- a child row carrying an existing ``rowid`` →
|
|
161
|
+
``{"rowid": id, "editType": 0, "newOldControl": [changed cells…]}``
|
|
162
|
+
— edits that row in place, preserving its id; only the supplied
|
|
163
|
+
fields are sent.
|
|
164
|
+
- a child row with no ``rowid`` →
|
|
165
|
+
``{"editType": 0, "newOldControl": [cells…]}`` — a fresh add.
|
|
166
|
+
- a child row flagged ``_delete`` → ``{"rowid": id, "editType": 2}``.
|
|
167
|
+
- rows not listed are left as-is (server keeps them).
|
|
168
|
+
- ``None`` / ``[]`` clears the whole sub-table:
|
|
169
|
+
``[{"rowid": "all", "editType": 2}]``.
|
|
170
|
+
|
|
171
|
+
Evidence: pd-openweb src/components/Form/core/utils.js,
|
|
172
|
+
formatControlToServer case 34 (the changes.updated branch builds
|
|
173
|
+
``{rowid, editType:0, newOldControl}`` for existing rows), plus a
|
|
174
|
+
captured UpdateWorksheetRow body editing one field of one child row
|
|
175
|
+
while leaving its sibling row untouched.
|
|
118
176
|
"""
|
|
119
177
|
rows = _sublist_parse_rows(ctrl, value)
|
|
120
|
-
|
|
178
|
+
# None or an explicit empty list means "clear every child row".
|
|
179
|
+
if not rows:
|
|
180
|
+
return json.dumps([{"rowid": "all", "editType": 2}],
|
|
181
|
+
ensure_ascii=False)
|
|
182
|
+
ops: list[dict[str, Any]] = []
|
|
121
183
|
for r in rows:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
184
|
+
rowid = _sublist_child_rowid(r)
|
|
185
|
+
if _sublist_row_is_delete(r):
|
|
186
|
+
if rowid:
|
|
187
|
+
ops.append({"rowid": rowid, "editType": 2})
|
|
188
|
+
continue
|
|
189
|
+
if rowid:
|
|
190
|
+
ops.append({
|
|
191
|
+
"rowid": rowid,
|
|
192
|
+
"editType": 0,
|
|
193
|
+
"newOldControl": _sublist_row_cells(
|
|
194
|
+
ctrl, r, temp_row_id=rowid),
|
|
195
|
+
})
|
|
196
|
+
else:
|
|
197
|
+
ops.append({
|
|
198
|
+
"editType": 0,
|
|
199
|
+
"newOldControl": _sublist_row_cells(ctrl, r),
|
|
200
|
+
})
|
|
126
201
|
return json.dumps(ops, ensure_ascii=False)
|
|
127
202
|
|
|
128
203
|
|
|
@@ -232,7 +307,8 @@ def encode_controls_by_id(
|
|
|
232
307
|
|
|
233
308
|
``for_update`` selects the UpdateWorksheetRow wire shapes where they
|
|
234
309
|
differ from AddWorksheetRow — today that is SUB_LIST (34), which on
|
|
235
|
-
update needs ``editType: 9`` plus
|
|
310
|
+
update needs ``editType: 9`` plus an incremental per-row op list
|
|
311
|
+
(in-place edits keyed by ``rowid``, fresh adds, and deletes — see
|
|
236
312
|
:func:`_sublist_update_value`).
|
|
237
313
|
"""
|
|
238
314
|
controls = ws_mod.get_worksheet_controls(session, worksheet_id)
|
|
@@ -138,6 +138,67 @@ class TestSaveChart:
|
|
|
138
138
|
assert body["appType"] == 2
|
|
139
139
|
assert body["reportType"] == 2
|
|
140
140
|
|
|
141
|
+
@patch("hap_cli.core.session.requests.post")
|
|
142
|
+
def test_split_control_id_auto_populates_top_level_split_id(self, mock_post):
|
|
143
|
+
# bug #036584: the statistics backend reads the split (拆分维度)
|
|
144
|
+
# control from the TOP-LEVEL `splitId` field, not from the nested
|
|
145
|
+
# `split.controlId`. A spec that only sets `split.controlId` (the
|
|
146
|
+
# natural shape) gets its split silently cleared on save. The core
|
|
147
|
+
# must mirror the controlId up to `splitId` so the split persists.
|
|
148
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
149
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
150
|
+
|
|
151
|
+
chart_mod.save_chart(
|
|
152
|
+
sess, app_id="ws1", name="Grouped", report_type=1,
|
|
153
|
+
extra={"split": {"controlId": "ctrlX", "controlType": 9}},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
body = mock_post.call_args[1]["json"]
|
|
157
|
+
assert body["splitId"] == "ctrlX"
|
|
158
|
+
assert body["split"]["controlId"] == "ctrlX"
|
|
159
|
+
|
|
160
|
+
@patch("hap_cli.core.session.requests.post")
|
|
161
|
+
def test_explicit_split_id_is_not_overwritten(self, mock_post):
|
|
162
|
+
# If the caller already pinned a top-level splitId, honor it.
|
|
163
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
164
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
165
|
+
|
|
166
|
+
chart_mod.save_chart(
|
|
167
|
+
sess, app_id="ws1", name="Grouped", report_type=1,
|
|
168
|
+
extra={
|
|
169
|
+
"splitId": "pinned",
|
|
170
|
+
"split": {"controlId": "ctrlX", "controlType": 9},
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert mock_post.call_args[1]["json"]["splitId"] == "pinned"
|
|
175
|
+
|
|
176
|
+
@patch("hap_cli.core.session.requests.post")
|
|
177
|
+
def test_no_split_means_no_split_id_injected(self, mock_post):
|
|
178
|
+
# A chart without a split block must not gain an empty splitId.
|
|
179
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
180
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
181
|
+
|
|
182
|
+
chart_mod.save_chart(
|
|
183
|
+
sess, app_id="ws1", name="Plain", report_type=1,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
assert "splitId" not in mock_post.call_args[1]["json"]
|
|
187
|
+
|
|
188
|
+
@patch("hap_cli.core.session.requests.post")
|
|
189
|
+
def test_empty_split_control_id_does_not_inject_split_id(self, mock_post):
|
|
190
|
+
# A split block with a blank controlId carries no dimension —
|
|
191
|
+
# don't fabricate a splitId from it.
|
|
192
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
193
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
194
|
+
|
|
195
|
+
chart_mod.save_chart(
|
|
196
|
+
sess, app_id="ws1", name="Plain", report_type=1,
|
|
197
|
+
extra={"split": {"controlId": ""}},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
assert "splitId" not in mock_post.call_args[1]["json"]
|
|
201
|
+
|
|
141
202
|
@patch("hap_cli.core.session.requests.post")
|
|
142
203
|
def test_extra_dict_is_merged_under_required_fields(self, mock_post):
|
|
143
204
|
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
@@ -248,6 +309,23 @@ class TestChartCLI:
|
|
|
248
309
|
body = mock_post.call_args[1]["json"]
|
|
249
310
|
assert body["id"] == "r1"
|
|
250
311
|
|
|
312
|
+
def test_report_type_help_maps_number_to_10_not_4(self, tmp_config):
|
|
313
|
+
# bug #036584: the old help text claimed "4=number" and shifted
|
|
314
|
+
# every type above it by one. The statistics module has no
|
|
315
|
+
# reportType 4 (the backend coerces a legacy 4 to 10), and the
|
|
316
|
+
# number chart (数值图) is 10. The help must not mislead users
|
|
317
|
+
# into passing 4.
|
|
318
|
+
result = CliRunner().invoke(cli, [
|
|
319
|
+
"worksheet", "chart", "create", "--help",
|
|
320
|
+
])
|
|
321
|
+
assert result.exit_code == 0, result.output
|
|
322
|
+
help_text = result.output
|
|
323
|
+
assert "4=number" not in help_text
|
|
324
|
+
assert "10=number" in help_text
|
|
325
|
+
# The genuinely correct anchors for the shifted-by-one values.
|
|
326
|
+
assert "5=radar" in help_text
|
|
327
|
+
assert "8=pivot" in help_text
|
|
328
|
+
|
|
251
329
|
@patch("hap_cli.core.session.requests.post")
|
|
252
330
|
def test_chart_delete_cli(self, mock_post, tmp_config):
|
|
253
331
|
Session(server_url="https://t.com", auth_token="tok").save()
|
|
@@ -599,13 +599,14 @@ class TestRecord:
|
|
|
599
599
|
|
|
600
600
|
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
601
601
|
@patch("hap_cli.core.session.requests.post")
|
|
602
|
-
def
|
|
602
|
+
def test_update_subtable_rows_without_rowid_are_adds(
|
|
603
603
|
self, mock_post, mock_controls, mock_session
|
|
604
604
|
):
|
|
605
605
|
# UpdateWorksheetRow must NOT receive the AddWorksheetRow 2-D
|
|
606
606
|
# array — the server answers "服务异常". The update wire shape is
|
|
607
|
-
# editType=9
|
|
608
|
-
# (
|
|
607
|
+
# editType=9 plus an incremental op list. Child rows with no
|
|
608
|
+
# rowid are fresh adds (editType 0, no delete-all op, no parent
|
|
609
|
+
# rowid), leaving any existing rows untouched (patch semantics).
|
|
609
610
|
mock_controls.return_value = self._SUBTABLE_CONTROLS
|
|
610
611
|
mock_post.return_value = _mock_response(True)
|
|
611
612
|
rec_mod.batch_update_by_name(
|
|
@@ -619,19 +620,97 @@ class TestRecord:
|
|
|
619
620
|
cell = rc["cDetail"]
|
|
620
621
|
assert cell["editType"] == 9
|
|
621
622
|
ops = json.loads(cell["value"])
|
|
622
|
-
assert ops
|
|
623
|
-
|
|
624
|
-
assert len(adds) == 2
|
|
625
|
-
for add in adds:
|
|
623
|
+
assert len(ops) == 2
|
|
624
|
+
for add in ops:
|
|
626
625
|
assert add["editType"] == 0
|
|
626
|
+
assert "rowid" not in add
|
|
627
627
|
cells = {c["controlId"]: c["value"]
|
|
628
628
|
for c in add["newOldControl"]}
|
|
629
629
|
assert cells["tempRowId"].startswith("temp-")
|
|
630
630
|
row0 = {c["controlId"]: c["value"]
|
|
631
|
-
for c in
|
|
631
|
+
for c in ops[0]["newOldControl"]}
|
|
632
632
|
assert row0["ch1"] == "苹果"
|
|
633
633
|
assert row0["ch2"] == "10"
|
|
634
634
|
|
|
635
|
+
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
636
|
+
@patch("hap_cli.core.session.requests.post")
|
|
637
|
+
def test_update_subtable_existing_row_edited_in_place(
|
|
638
|
+
self, mock_post, mock_controls, mock_session
|
|
639
|
+
):
|
|
640
|
+
# A child row carrying an existing rowid is edited in place: the
|
|
641
|
+
# op keeps that rowid (editType 0) and the tempRowId cell repeats
|
|
642
|
+
# it, so the server preserves the row id instead of minting a new
|
|
643
|
+
# one. Only the supplied field is sent (bug feedback: in-place
|
|
644
|
+
# edits previously dropped the child rowid by full-replace).
|
|
645
|
+
mock_controls.return_value = self._SUBTABLE_CONTROLS
|
|
646
|
+
mock_post.return_value = _mock_response(True)
|
|
647
|
+
rec_mod.batch_update_by_name(
|
|
648
|
+
mock_session, "ws1",
|
|
649
|
+
[{"rowid": "row-1",
|
|
650
|
+
"明细": [{"rowid": "child-1", "数量": 46}]}],
|
|
651
|
+
)
|
|
652
|
+
rc = {c["controlId"]: c
|
|
653
|
+
for c in mock_post.call_args[1]["json"]["newOldControl"]}
|
|
654
|
+
cell = rc["cDetail"]
|
|
655
|
+
assert cell["editType"] == 9
|
|
656
|
+
ops = json.loads(cell["value"])
|
|
657
|
+
assert len(ops) == 1
|
|
658
|
+
op = ops[0]
|
|
659
|
+
assert op["rowid"] == "child-1"
|
|
660
|
+
assert op["editType"] == 0
|
|
661
|
+
cells = {c["controlId"]: c["value"] for c in op["newOldControl"]}
|
|
662
|
+
assert cells["tempRowId"] == "child-1"
|
|
663
|
+
assert cells["ch2"] == "46"
|
|
664
|
+
# Only the changed field is sent — the untouched 品名 is absent.
|
|
665
|
+
assert "ch1" not in cells
|
|
666
|
+
|
|
667
|
+
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
668
|
+
@patch("hap_cli.core.session.requests.post")
|
|
669
|
+
def test_update_subtable_mixed_edit_add_delete(
|
|
670
|
+
self, mock_post, mock_controls, mock_session
|
|
671
|
+
):
|
|
672
|
+
# One op list may mix an in-place edit, a fresh add, and a delete
|
|
673
|
+
# marker. Untouched rows are simply omitted.
|
|
674
|
+
mock_controls.return_value = self._SUBTABLE_CONTROLS
|
|
675
|
+
mock_post.return_value = _mock_response(True)
|
|
676
|
+
rec_mod.batch_update_by_name(
|
|
677
|
+
mock_session, "ws1",
|
|
678
|
+
[{"rowid": "row-1",
|
|
679
|
+
"明细": [
|
|
680
|
+
{"rowid": "child-1", "数量": 5},
|
|
681
|
+
{"品名": "新行"},
|
|
682
|
+
{"rowid": "child-2", "_delete": True},
|
|
683
|
+
]}],
|
|
684
|
+
)
|
|
685
|
+
rc = {c["controlId"]: c
|
|
686
|
+
for c in mock_post.call_args[1]["json"]["newOldControl"]}
|
|
687
|
+
ops = json.loads(rc["cDetail"]["value"])
|
|
688
|
+
assert ops[0]["rowid"] == "child-1" and ops[0]["editType"] == 0
|
|
689
|
+
assert "rowid" not in ops[1] and ops[1]["editType"] == 0
|
|
690
|
+
assert ops[2] == {"rowid": "child-2", "editType": 2}
|
|
691
|
+
|
|
692
|
+
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
693
|
+
@patch("hap_cli.core.session.requests.post")
|
|
694
|
+
def test_update_subtable_temp_rowid_treated_as_add(
|
|
695
|
+
self, mock_post, mock_controls, mock_session
|
|
696
|
+
):
|
|
697
|
+
# A temp-/default- prefixed rowid names a not-yet-persisted row,
|
|
698
|
+
# so it is a fresh add, not an in-place edit.
|
|
699
|
+
mock_controls.return_value = self._SUBTABLE_CONTROLS
|
|
700
|
+
mock_post.return_value = _mock_response(True)
|
|
701
|
+
rec_mod.batch_update_by_name(
|
|
702
|
+
mock_session, "ws1",
|
|
703
|
+
[{"rowid": "row-1",
|
|
704
|
+
"明细": [{"rowid": "temp-abc", "品名": "梨"}]}],
|
|
705
|
+
)
|
|
706
|
+
rc = {c["controlId"]: c
|
|
707
|
+
for c in mock_post.call_args[1]["json"]["newOldControl"]}
|
|
708
|
+
op = json.loads(rc["cDetail"]["value"])[0]
|
|
709
|
+
assert "rowid" not in op
|
|
710
|
+
assert op["editType"] == 0
|
|
711
|
+
cells = {c["controlId"]: c["value"] for c in op["newOldControl"]}
|
|
712
|
+
assert cells["tempRowId"].startswith("temp-")
|
|
713
|
+
|
|
635
714
|
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
636
715
|
@patch("hap_cli.core.session.requests.post")
|
|
637
716
|
def test_update_subtable_empty_list_clears_rows(
|
|
@@ -666,9 +745,12 @@ class TestRecord:
|
|
|
666
745
|
cell = out[0]
|
|
667
746
|
assert cell["editType"] == 9
|
|
668
747
|
ops = json.loads(cell["value"])
|
|
669
|
-
|
|
748
|
+
# No rowid given -> a fresh add, no delete-all op.
|
|
749
|
+
assert len(ops) == 1
|
|
750
|
+
assert ops[0]["editType"] == 0
|
|
751
|
+
assert "rowid" not in ops[0]
|
|
670
752
|
row0 = {c["controlId"]: c["value"]
|
|
671
|
-
for c in ops[
|
|
753
|
+
for c in ops[0]["newOldControl"]}
|
|
672
754
|
assert row0["ch1"] == "梨"
|
|
673
755
|
|
|
674
756
|
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
@@ -721,8 +803,11 @@ class TestRecord:
|
|
|
721
803
|
rc = {c["controlId"]: c
|
|
722
804
|
for c in mock_post.call_args[1]["json"]["newOldControl"]}
|
|
723
805
|
assert rc["cDetail"]["editType"] == 9
|
|
724
|
-
|
|
725
|
-
|
|
806
|
+
ops = json.loads(rc["cDetail"]["value"])
|
|
807
|
+
# No rowid -> fresh add (patch semantics), no delete-all op.
|
|
808
|
+
assert len(ops) == 1
|
|
809
|
+
assert ops[0]["editType"] == 0
|
|
810
|
+
assert "rowid" not in ops[0]
|
|
726
811
|
|
|
727
812
|
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
728
813
|
@patch("hap_cli.core.session.requests.post")
|
|
@@ -744,8 +829,10 @@ class TestRecord:
|
|
|
744
829
|
for c in mock_post.call_args[1]["json"]["newOldControl"]}
|
|
745
830
|
assert rc["cDetail"]["editType"] == 9
|
|
746
831
|
ops = json.loads(rc["cDetail"]["value"])
|
|
747
|
-
|
|
748
|
-
assert len(ops) ==
|
|
832
|
+
# No rowid -> fresh add (patch semantics), no delete-all op.
|
|
833
|
+
assert len(ops) == 1
|
|
834
|
+
assert ops[0]["editType"] == 0
|
|
835
|
+
assert "rowid" not in ops[0]
|
|
749
836
|
|
|
750
837
|
@patch("hap_cli.core.worksheet.get_worksheet_controls")
|
|
751
838
|
@patch("hap_cli.core.session.requests.post")
|
|
@@ -8968,6 +9055,53 @@ class TestChatbot:
|
|
|
8968
9055
|
assert body["appSectionId"] == "sec1"
|
|
8969
9056
|
assert body["type"] == 1
|
|
8970
9057
|
assert body["isPermanentlyDelete"] is False
|
|
9058
|
+
# explicit section -> no GetApp lookup, single call
|
|
9059
|
+
assert mock_post.call_count == 1
|
|
9060
|
+
|
|
9061
|
+
@patch("hap_cli.core.session.requests.post")
|
|
9062
|
+
def test_delete_chatbot_auto_resolves_section(self, mock_post, mock_session):
|
|
9063
|
+
# appSectionId is required in practice (the UI always sends
|
|
9064
|
+
# parentGroupId||groupId; the server returns 服务异常 without it). When
|
|
9065
|
+
# the caller omits section_id we resolve it from the app structure
|
|
9066
|
+
# (GetApp) and include the discovered appSectionId on the delete.
|
|
9067
|
+
mock_post.side_effect = [
|
|
9068
|
+
_mock_response({"sections": [
|
|
9069
|
+
{"appSectionId": "secA",
|
|
9070
|
+
"workSheetInfo": [{"workSheetId": "other"}]},
|
|
9071
|
+
{"appSectionId": "secB",
|
|
9072
|
+
"workSheetInfo": [{"workSheetId": "cb1"}]},
|
|
9073
|
+
]}),
|
|
9074
|
+
_mock_response(True),
|
|
9075
|
+
]
|
|
9076
|
+
chatbot_mod.delete_chatbot(
|
|
9077
|
+
mock_session, chatbot_id="cb1", app_id="a1", project_id="p1",
|
|
9078
|
+
)
|
|
9079
|
+
assert mock_post.call_count == 2
|
|
9080
|
+
assert mock_post.call_args_list[0].args[0].endswith("/HomeApp/GetApp")
|
|
9081
|
+
del_body = mock_post.call_args_list[1].kwargs["json"]
|
|
9082
|
+
assert mock_post.call_args_list[1].args[0].endswith(
|
|
9083
|
+
"/AppManagement/RemoveWorkSheetForApp")
|
|
9084
|
+
assert del_body["appSectionId"] == "secB"
|
|
9085
|
+
assert del_body["workSheetId"] == "cb1"
|
|
9086
|
+
assert del_body["type"] == 1
|
|
9087
|
+
|
|
9088
|
+
@patch("hap_cli.core.session.requests.post")
|
|
9089
|
+
def test_delete_chatbot_auto_resolve_nested_section(self, mock_post, mock_session):
|
|
9090
|
+
# The chatbot may live in a nested childSection.
|
|
9091
|
+
mock_post.side_effect = [
|
|
9092
|
+
_mock_response({"sections": [
|
|
9093
|
+
{"appSectionId": "top", "workSheetInfo": [],
|
|
9094
|
+
"childSections": [
|
|
9095
|
+
{"appSectionId": "child1",
|
|
9096
|
+
"workSheetInfo": [{"workSheetId": "cb1"}]}]},
|
|
9097
|
+
]}),
|
|
9098
|
+
_mock_response(True),
|
|
9099
|
+
]
|
|
9100
|
+
chatbot_mod.delete_chatbot(
|
|
9101
|
+
mock_session, chatbot_id="cb1", app_id="a1", project_id="p1",
|
|
9102
|
+
)
|
|
9103
|
+
del_body = mock_post.call_args_list[1].kwargs["json"]
|
|
9104
|
+
assert del_body["appSectionId"] == "child1"
|
|
8971
9105
|
|
|
8972
9106
|
def test_cli_delete_routes(self, tmp_config):
|
|
8973
9107
|
runner = CliRunner()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|