hap-cli 0.8.7__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.7 → hap_cli-0.8.9}/PKG-INFO +1 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/chart_cmd.py +7 -5
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/chatbot_cmd.py +2 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/icon_cmd.py +17 -11
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/record_cmd.py +6 -3
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/chart.py +19 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/chatbot.py +46 -9
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/record.py +90 -14
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/worksheet_templates.py +7 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_chart.py +78 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_core.py +195 -14
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.7 → hap_cli-0.8.9}/MANIFEST.in +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/README.md +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/README.md +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/context.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/SOURCES.txt +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/charts.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/config.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/errors.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/executor.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/fields.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/hap.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/schema.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/step.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/steps.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/store.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/setup.cfg +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/setup.py +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.7 → hap_cli-0.8.9}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.7 → 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")
|
|
@@ -31,9 +31,10 @@ def _icon_table(rows):
|
|
|
31
31
|
@click.option(
|
|
32
32
|
"--limit",
|
|
33
33
|
type=int,
|
|
34
|
-
default=
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
default=None,
|
|
35
|
+
help="Maximum number of icons to return. Caps the result exactly — "
|
|
36
|
+
"--limit 5 returns at most 5. Defaults to 20 for a keyword search, "
|
|
37
|
+
"or the whole catalogue when browsing with no keyword.",
|
|
37
38
|
)
|
|
38
39
|
@pass_context
|
|
39
40
|
def icon_search(ctx, query, limit):
|
|
@@ -41,19 +42,24 @@ def icon_search(ctx, query, limit):
|
|
|
41
42
|
|
|
42
43
|
Pass one or more keywords; an icon must relate to every keyword to
|
|
43
44
|
match. With no keyword, lists the whole catalogue. When fewer than
|
|
44
|
-
10 icons match, random suggestions top the list up to 10
|
|
45
|
-
always have something to pick from. Use the
|
|
46
|
-
the --icon option of app/worksheet creation.
|
|
45
|
+
10 icons match, random suggestions top the list up to 10 (never more
|
|
46
|
+
than --limit) so you always have something to pick from. Use the
|
|
47
|
+
returned icon name with the --icon option of app/worksheet creation.
|
|
47
48
|
"""
|
|
48
49
|
try:
|
|
49
|
-
# With no keyword the command acts as "browse all"; the limit
|
|
50
|
-
# only narrows an actual keyword search.
|
|
51
|
-
effective_limit = limit if query else None
|
|
52
|
-
rows = ic_mod.search_icons(list(query), limit=effective_limit)
|
|
53
50
|
if query:
|
|
51
|
+
# Keyword search: default to 20 matches; an explicit --limit
|
|
52
|
+
# is the exact upper bound, and the random-suggestion floor
|
|
53
|
+
# never exceeds it either.
|
|
54
|
+
effective_limit = limit if limit is not None else 20
|
|
55
|
+
rows = ic_mod.search_icons(list(query), limit=effective_limit)
|
|
54
56
|
rows = ic_mod.pad_with_random_icons(
|
|
55
|
-
rows, target=min(10, max(0,
|
|
57
|
+
rows, target=min(10, max(0, effective_limit))
|
|
56
58
|
)
|
|
59
|
+
else:
|
|
60
|
+
# Browse all by default; honor an explicit --limit if given.
|
|
61
|
+
# search_icons treats None as "no truncation".
|
|
62
|
+
rows = ic_mod.search_icons(list(query), limit=limit)
|
|
57
63
|
ctx.output(rows, _icon_table)
|
|
58
64
|
except Exception as e:
|
|
59
65
|
ctx.handle_error(e)
|
|
@@ -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)
|
|
@@ -407,8 +407,14 @@ def build_control(
|
|
|
407
407
|
spec = dict(spec)
|
|
408
408
|
child_type = spec.pop("type")
|
|
409
409
|
child_name = spec.pop("name")
|
|
410
|
+
# row/col default to the child's position in the list, but a
|
|
411
|
+
# child spec may pin its own grid coordinates (the skill emits
|
|
412
|
+
# row/size per child). Seed defaults then let the spec win, so
|
|
413
|
+
# an explicit `row`/`col` in spec does not collide with a
|
|
414
|
+
# positional kwarg ("multiple values for 'row'").
|
|
415
|
+
child_kwargs = {"row": i, "col": 0, **spec}
|
|
410
416
|
built.append(build_control(
|
|
411
|
-
child_type, child_name,
|
|
417
|
+
child_type, child_name, **child_kwargs,
|
|
412
418
|
))
|
|
413
419
|
relation_controls = built
|
|
414
420
|
|
|
@@ -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()
|