hap-cli 0.8.25__tar.gz → 0.8.26__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.25 → hap_cli-0.8.26}/PKG-INFO +1 -1
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/chart_cmd.py +5 -2
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/record_cmd.py +14 -2
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/chart.py +30 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/record.py +7 -0
- hap_cli-0.8.26/hap_cli/guide/en/chart.md +150 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/index.json +8 -0
- hap_cli-0.8.26/hap_cli/guide/zh-Hans/chart.md +133 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/locale/messages.json +9 -5
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_chart.py +33 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_core.py +24 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/SOURCES.txt +2 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/MANIFEST.in +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/README.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/README.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/context.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/charts.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/config.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/errors.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/executor.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/fields.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/hap.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/schema.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/step.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/steps.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/store.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/setup.cfg +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/setup.py +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.25 → hap_cli-0.8.26}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -123,7 +123,8 @@ def _resolve_app_id_for_filter(session, worksheet_id: str, extra: dict) -> str:
|
|
|
123
123
|
return (data or {}).get("appId", "") if isinstance(data, dict) else ""
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
@chart.command("create"
|
|
126
|
+
@chart.command("create", help="Create a new chart on a worksheet. "
|
|
127
|
+
"Chart spec shapes are in `hap guide chart`.")
|
|
127
128
|
@click.argument("worksheet_id")
|
|
128
129
|
@click.option("--name", required=True, help="Chart display name.")
|
|
129
130
|
@click.option("--report-type", "report_type", type=int, required=True,
|
|
@@ -163,7 +164,9 @@ def chart_create(ctx, worksheet_id, name, report_type, app_type, app_id,
|
|
|
163
164
|
ctx.handle_error(e)
|
|
164
165
|
|
|
165
166
|
|
|
166
|
-
@chart.command("update"
|
|
167
|
+
@chart.command("update", help="Update an existing chart's configuration. "
|
|
168
|
+
"Read the config with `chart get`, edit it, submit "
|
|
169
|
+
"it back — see `hap guide chart`.")
|
|
167
170
|
@click.argument("report_id")
|
|
168
171
|
@click.option("--app-id", "-a", required=True, help="Worksheet ID the chart attaches to.")
|
|
169
172
|
@click.option("--name", required=True, help="Chart display name.")
|
|
@@ -374,16 +374,28 @@ def record_discussions(ctx, worksheet_id, row_id, page_size, page):
|
|
|
374
374
|
@click.option("--app-id", default="", help="Application ID (auto-detected from the worksheet if omitted)")
|
|
375
375
|
@click.option("--view-id", default="", help="View ID the discussion originates from")
|
|
376
376
|
@click.option("--reply-id", default="", help="ID of the discussion being replied to")
|
|
377
|
+
@click.option("--file", "-f", "files", multiple=True,
|
|
378
|
+
help="Attach a file to the comment (repeatable). Accepts a local "
|
|
379
|
+
"path or an http(s) URL.")
|
|
377
380
|
@pass_context
|
|
378
|
-
def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id):
|
|
381
|
+
def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id, files):
|
|
379
382
|
"""Add a discussion/comment to a record."""
|
|
380
383
|
try:
|
|
381
384
|
session = ctx.get_session()
|
|
385
|
+
attachments = None
|
|
386
|
+
if files:
|
|
387
|
+
from hap_cli.core import upload as up_mod
|
|
388
|
+
descriptors = up_mod.source_descriptors(list(files))
|
|
389
|
+
attachments = up_mod.upload_files(
|
|
390
|
+
session, descriptors,
|
|
391
|
+
worksheet_id=worksheet_id, app_id=app_id,
|
|
392
|
+
)
|
|
382
393
|
data = rec_mod.add_discussion(
|
|
383
394
|
session, worksheet_id, row_id, message,
|
|
384
395
|
app_id=app_id, view_id=view_id, reply_id=reply_id,
|
|
396
|
+
attachments=attachments,
|
|
385
397
|
)
|
|
386
|
-
ctx.output(data, lambda d: click.echo(
|
|
398
|
+
ctx.output(data, lambda d: click.echo("Discussion added."))
|
|
387
399
|
except Exception as e:
|
|
388
400
|
ctx.handle_error(e)
|
|
389
401
|
|
|
@@ -44,6 +44,30 @@ from hap_cli.core.session import Session
|
|
|
44
44
|
|
|
45
45
|
logger = logging.getLogger("hap_cli.chart")
|
|
46
46
|
|
|
47
|
+
# A chart that is saved without a ``filter`` block saves successfully but
|
|
48
|
+
# never renders: the statistics backend cannot scope the query and
|
|
49
|
+
# ``report/getData`` answers status -1/0, which the UI paints as
|
|
50
|
+
# "无法形成图表 / 构成要素不存在或已删除". The chart looks fine in the editor
|
|
51
|
+
# (which supplies its own default scope) and only breaks after save — the
|
|
52
|
+
# single most common way a hand-written ``chart create`` spec silently
|
|
53
|
+
# produces a blank chart. The frontend's ``getNewReport`` always injects a
|
|
54
|
+
# default time filter for a new worksheet chart; we mirror that here, but
|
|
55
|
+
# default to ``rangeType: 0`` (全部时间 / all-time) instead of the UI's
|
|
56
|
+
# "last 365 days" so the chart is never blank merely because the default
|
|
57
|
+
# window excluded older data. Callers that supply their own ``filter``
|
|
58
|
+
# (any non-empty dict) keep it untouched.
|
|
59
|
+
_DEFAULT_CHART_FILTER: dict[str, Any] = {
|
|
60
|
+
"filterRangeId": "ctime",
|
|
61
|
+
"filterRangeName": "创建时间",
|
|
62
|
+
"rangeType": 0,
|
|
63
|
+
"rangeValue": None,
|
|
64
|
+
"today": False,
|
|
65
|
+
"dynamicFilter": {
|
|
66
|
+
"startType": 1, "startCount": 1, "startUnit": 1,
|
|
67
|
+
"endType": 1, "endCount": 1, "endUnit": 1,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
47
71
|
|
|
48
72
|
def list_charts(
|
|
49
73
|
session: Session,
|
|
@@ -218,6 +242,12 @@ def save_chart(
|
|
|
218
242
|
split_control_id = body["split"].get("controlId")
|
|
219
243
|
if split_control_id:
|
|
220
244
|
body["splitId"] = split_control_id
|
|
245
|
+
# A chart with no filter block saves but never renders (blank after
|
|
246
|
+
# save — see _DEFAULT_CHART_FILTER). Inject an all-time default when the
|
|
247
|
+
# caller omitted it; a caller-supplied filter (any non-empty value) is
|
|
248
|
+
# left untouched so an explicit scope always wins.
|
|
249
|
+
if not body.get("filter"):
|
|
250
|
+
body["filter"] = dict(_DEFAULT_CHART_FILTER)
|
|
221
251
|
# Resolve the chart's filter block before sending saveReportConfig.
|
|
222
252
|
if isinstance(body.get("filter"), dict):
|
|
223
253
|
body["filter"] = _ensure_filter_id(
|
|
@@ -1089,6 +1089,7 @@ def add_discussion(
|
|
|
1089
1089
|
app_id: str = "",
|
|
1090
1090
|
view_id: str = "",
|
|
1091
1091
|
reply_id: str = "",
|
|
1092
|
+
attachments: Optional[list[dict[str, Any]]] = None,
|
|
1092
1093
|
) -> dict[str, Any]:
|
|
1093
1094
|
"""Add a discussion/comment to a record (sourceType 8 / worksheet-row).
|
|
1094
1095
|
|
|
@@ -1117,6 +1118,12 @@ def add_discussion(
|
|
|
1117
1118
|
}
|
|
1118
1119
|
if reply_id:
|
|
1119
1120
|
data["replyId"] = reply_id
|
|
1121
|
+
# ``attachments`` carries uploaded-file descriptors (from
|
|
1122
|
+
# hap_cli.core.upload). The frontend WorkSheetCommenter sends them as a
|
|
1123
|
+
# JSON-encoded string under ``attachments`` (see pd-openweb
|
|
1124
|
+
# components/comment/commenter.jsx), so match that shape.
|
|
1125
|
+
if attachments:
|
|
1126
|
+
data["attachments"] = json.dumps(attachments, ensure_ascii=False)
|
|
1120
1127
|
result = session.api_call("Discussion", "AddDiscussion", data)
|
|
1121
1128
|
_ensure_discussion_ok(result, "add discussion")
|
|
1122
1129
|
return result
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Charts: create, update, place on a custom page
|
|
2
|
+
|
|
3
|
+
`hap worksheet chart` builds a chart (report) on a worksheet; `hap custom-page`
|
|
4
|
+
places charts into a custom page to form a dashboard. This topic covers how to
|
|
5
|
+
shape a chart spec, the right way to edit a chart, and the most common trap —
|
|
6
|
+
"saved fine, blank on the page".
|
|
7
|
+
|
|
8
|
+
> 🚨 Core risk of this topic: **an under-specified chart still saves
|
|
9
|
+
> successfully but renders blank**, showing "无法形成图表 / 构成要素不存在或已删除"
|
|
10
|
+
> (cannot draw chart / component missing or deleted). It looks right in the
|
|
11
|
+
> editor and only breaks after save. Root cause is in section 3 — the CLI now
|
|
12
|
+
> backfills the missing filter scope, but you still need the model.
|
|
13
|
+
|
|
14
|
+
## 1. What a chart is made of
|
|
15
|
+
|
|
16
|
+
A chart hangs off **one worksheet** and needs at least three things:
|
|
17
|
+
|
|
18
|
+
- **Data source**: the worksheet ID (`WORKSHEET_ID` in the command, `appId` in
|
|
19
|
+
the spec). Note HAP's chart spec calls the worksheet `appId` — it is NOT the
|
|
20
|
+
application ID. When editing a chart, do not fill this with the app ID.
|
|
21
|
+
- **Dimension `xaxes`**: the grouping, e.g. by date (year) or by status. A
|
|
22
|
+
number chart (`report_type=10`) may have no dimension — just a value.
|
|
23
|
+
- **Value `yaxisList`**: what to aggregate, e.g. sum of amount, record count.
|
|
24
|
+
|
|
25
|
+
Plus a **required filter scope `filter`** (section 3).
|
|
26
|
+
|
|
27
|
+
## 2. Chart types (reportType integer)
|
|
28
|
+
|
|
29
|
+
Pass an integer to `--report-type`. **There is no 4** — an old API doc labels 4
|
|
30
|
+
as the number chart, which is wrong; the number chart is **10**.
|
|
31
|
+
|
|
32
|
+
| # | type | # | type |
|
|
33
|
+
|---|------|---|------|
|
|
34
|
+
| 1 | column | 10 | number |
|
|
35
|
+
| 2 | line | 11 | bidirectional bar |
|
|
36
|
+
| 3 | pie | 12 | scatter |
|
|
37
|
+
| 5 | radar | 13 | word cloud |
|
|
38
|
+
| 6 | funnel | 14 | gauge |
|
|
39
|
+
| 7 | dual-axis | 15 | progress |
|
|
40
|
+
| 8 | pivot | 16 | ranking |
|
|
41
|
+
| 9 | region map | 17 | world map |
|
|
42
|
+
|
|
43
|
+
## 3. Why "saved but blank": a missing filter
|
|
44
|
+
|
|
45
|
+
A chart **saves successfully even with no `filter` block** (`create` still
|
|
46
|
+
returns a `reportId`), but at render time the backend cannot scope the query and
|
|
47
|
+
`report/getData` returns status -1/0, so the page shows "cannot draw chart". This
|
|
48
|
+
is exactly the "fine in editor, blank after save" symptom: the editor supplies
|
|
49
|
+
its own default scope, the stored spec does not.
|
|
50
|
+
|
|
51
|
+
**The CLI now injects a default all-time filter when you omit `filter`**, so a
|
|
52
|
+
plain `chart create` no longer goes blank. You only need to write `filter`
|
|
53
|
+
yourself to **restrict the time range**:
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
"filter": {
|
|
57
|
+
"filterRangeId": "ctime", // which date field to scope by; "ctime"=create time
|
|
58
|
+
"filterRangeName": "创建时间",
|
|
59
|
+
"rangeType": 0, // 0=all time; 8=this month; 18=last N days (with rangeValue)
|
|
60
|
+
"rangeValue": null,
|
|
61
|
+
"today": false
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Common `rangeType`: `0` all, `4` this week, `8` this month, `11` this quarter,
|
|
66
|
+
`15` this year, `16` last year, `18` last N days (`rangeValue` = day count, e.g. 365).
|
|
67
|
+
|
|
68
|
+
## 4. Create: `hap worksheet chart create`
|
|
69
|
+
|
|
70
|
+
Pass the spec via `-j/--spec-json` (inline) or `-f/--spec-file` (file). Reference
|
|
71
|
+
fields by **controlId**; read fields with `hap worksheet fields WS_ID`.
|
|
72
|
+
|
|
73
|
+
**Column chart (amount by status):**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
hap worksheet chart create WS_ID --name "Amount by status" --report-type 1 -j '{
|
|
77
|
+
"xaxes": {"controlId": "<status field>", "controlType": 11},
|
|
78
|
+
"yaxisList": [{"controlId": "<amount field>", "controlType": 6, "normType": 1}]
|
|
79
|
+
}'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Number chart (no dimension, one value):** `--report-type 10`, spec with only `yaxisList`.
|
|
83
|
+
|
|
84
|
+
`yaxisList[].normType` aggregation: `1` sum, `2` max, `3` min, `4` avg, `5` count,
|
|
85
|
+
`6` distinct count. Numeric fields default to `1`, text/option fields use `5`.
|
|
86
|
+
Record count uses the special column
|
|
87
|
+
`{"controlId": "record_count", "controlName": "记录数量", "controlType": 10000000, "normType": 5}`.
|
|
88
|
+
|
|
89
|
+
**Date dimension**: add `particleSizeType` to `xaxes`: `5` year, `4` quarter,
|
|
90
|
+
`3` month, `2` week, `1` day.
|
|
91
|
+
|
|
92
|
+
**Condition filter** (not a time range but "status = done"): put a high-level
|
|
93
|
+
`items` array in `filter`; the CLI stores it as a saved filter and backfills the id:
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
"filter": {"items": [{"controlId": "<status field>", "dataType": 11, "spliceType": 1,
|
|
97
|
+
"filterType": 2, "values": ["<option key>"]}]}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When using `items`, pass `--app-id <application id>` (the app, not the worksheet)
|
|
101
|
+
so the CLI can store the filter.
|
|
102
|
+
|
|
103
|
+
## 5. Update: read the config, change a few params, submit back
|
|
104
|
+
|
|
105
|
+
You don't rebuild a spec from scratch. **Read the current config, change what you
|
|
106
|
+
need, submit the whole thing back:**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 1. read the current config
|
|
110
|
+
hap --json worksheet chart get REPORT_ID --app-id WS_ID > chart.json
|
|
111
|
+
# 2. edit chart.json (name / normType / xaxes / add a filter, etc.)
|
|
112
|
+
# 3. submit it back (update needs --app-id=worksheetId, --name, --report-type)
|
|
113
|
+
hap worksheet chart update REPORT_ID --app-id WS_ID --name "New name" --report-type 1 -f chart.json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Extra fields `get` returns (`controls`, `account`, `createdDate`) can be passed
|
|
117
|
+
back as-is; the server ignores them. `update` replaces the whole config, so edit
|
|
118
|
+
on top of the read config — don't send only the fields you changed.
|
|
119
|
+
|
|
120
|
+
## 6. Place on a custom page (dashboard)
|
|
121
|
+
|
|
122
|
+
Once the chart exists and you have its `reportId`, add it as a component with
|
|
123
|
+
`custom-page save`.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# page version: read it first with hap --json custom-page info PAGE_ID, pass the same value
|
|
127
|
+
hap custom-page save PAGE_ID --version <current version> --components '[
|
|
128
|
+
{"type": 1, "value": "<reportId>", "worksheetId": "<WS_ID>",
|
|
129
|
+
"name": "Amount by status", "reportType": 1,
|
|
130
|
+
"config": {"objectId": "<32 hex chars>"},
|
|
131
|
+
"web": {"title": "", "titleVisible": false, "visible": true,
|
|
132
|
+
"layout": {"x": 0, "y": 0, "w": 24, "h": 10, "minW": 2, "minH": 4}},
|
|
133
|
+
"mobile": {"title": "", "titleVisible": false, "visible": true, "layout": null}}
|
|
134
|
+
]'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Key points:
|
|
138
|
+
|
|
139
|
+
- A chart component has `type` **1**, `value` = `reportId`, plus `worksheetId`
|
|
140
|
+
and `reportType`.
|
|
141
|
+
- `config.objectId` is a unique 32-hex string per component (used by linked filters).
|
|
142
|
+
- Layout is a **48-column grid**: `w` max 48; two charts per row at `w:24` each.
|
|
143
|
+
- `--version` must equal the page's current version, else "自定义页面保存失败";
|
|
144
|
+
a successful save bumps version by 1.
|
|
145
|
+
- `save` replaces the whole page: to keep existing components, read them via
|
|
146
|
+
`custom-page info` first and append the new one before submitting.
|
|
147
|
+
|
|
148
|
+
For building a whole app with dashboards, prefer the `hap-cli-app-creator` skill:
|
|
149
|
+
it generates full chart specs and page layouts from a friendly config. Use the
|
|
150
|
+
commands above to hand-edit a single chart or add one chart to an existing page.
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
"zh-Hans": "顺着通知或讨论找到它所属的那条应用记录。"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
"name": "chart",
|
|
29
|
+
"title": {"en": "Charts and dashboards", "zh-Hans": "统计图与看板"},
|
|
30
|
+
"summary": {
|
|
31
|
+
"en": "Chart spec shape, editing a chart, placing it on a custom page. Miss a field and it saves fine but renders blank.",
|
|
32
|
+
"zh-Hans": "图表规格怎么写、怎么改图、怎么放进自定义页。写少了照样保存成功,页面却空白。"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
27
35
|
{
|
|
28
36
|
"name": "skills",
|
|
29
37
|
"title": {"en": "When to hand off", "zh-Hans": "什么时候转交专项 skill"},
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# 统计图:创建、修改、放进自定义页
|
|
2
|
+
|
|
3
|
+
`hap worksheet chart` 在工作表上建统计图(报表),`hap custom-page` 把图放进自定义页
|
|
4
|
+
组成看板。本主题讲清楚图表规格该怎么写、改图的正确姿势,以及最容易踩的「保存成功、
|
|
5
|
+
页面空白」这个坑。
|
|
6
|
+
|
|
7
|
+
> 🚨 本主题核心风险:**图表规格写少了照样保存成功,但页面上一片空白**,提示
|
|
8
|
+
> 「无法形成图表 / 构成要素不存在或已删除」。编辑界面能正常显示,一保存就没了。
|
|
9
|
+
> 根因见下面第 3 节——现在 CLI 会自动补上缺失的筛选范围,但你仍要理解这个模型。
|
|
10
|
+
|
|
11
|
+
## 1. 一张图由什么组成
|
|
12
|
+
|
|
13
|
+
统计图挂在**一张工作表**上,最少需要三件东西:
|
|
14
|
+
|
|
15
|
+
- **数据源**:工作表 ID(就是命令里的 `WORKSHEET_ID` / 规格里的 `appId`)。
|
|
16
|
+
注意 HAP 的图表规格里 `appId` 指的是**工作表**,不是应用;改图时不要填成应用 ID。
|
|
17
|
+
- **维度 `xaxes`**:分组依据,比如按「日期(年)」「状态」分。数值图(`report_type=10`)
|
|
18
|
+
可以没有维度,只放数值。
|
|
19
|
+
- **数值 `yaxisList`**:要统计的量,比如「金额求和」「记录数量」。
|
|
20
|
+
|
|
21
|
+
此外还有一个**必须存在的筛选范围 `filter`**(见第 3 节)。
|
|
22
|
+
|
|
23
|
+
## 2. 图表类型(reportType 整数)
|
|
24
|
+
|
|
25
|
+
`--report-type` 传整数。**没有 4**——老接口文档把 4 标成数值图是错的,数值图是 **10**。
|
|
26
|
+
|
|
27
|
+
| 值 | 类型 | 值 | 类型 |
|
|
28
|
+
|----|------|----|------|
|
|
29
|
+
| 1 | 柱状图 | 10 | 数值图 |
|
|
30
|
+
| 2 | 折线图 | 11 | 双向条形图 |
|
|
31
|
+
| 3 | 饼图 | 12 | 散点图 |
|
|
32
|
+
| 5 | 雷达图 | 13 | 词云 |
|
|
33
|
+
| 6 | 漏斗图 | 14 | 仪表盘 |
|
|
34
|
+
| 7 | 双轴图 | 15 | 进度图 |
|
|
35
|
+
| 8 | 透视表 | 16 | 排行榜 |
|
|
36
|
+
| 9 | 行政区域地图 | 17 | 世界地图 |
|
|
37
|
+
|
|
38
|
+
## 3. 「保存成功却空白」的根因:缺 filter
|
|
39
|
+
|
|
40
|
+
一张图**没有 `filter` 块也能保存成功**(`create` 照样返回 `reportId`),但渲染时后端
|
|
41
|
+
无法给查询定范围,`report/getData` 返回状态 -1/0,页面就显示「无法形成图表」。这正是
|
|
42
|
+
「编辑正常、保存后空白」的原因:编辑器有自己的默认范围,存下来的规格却没有。
|
|
43
|
+
|
|
44
|
+
**现在 CLI 会在你没写 `filter` 时自动补一个「全部时间」的默认范围**,所以直接
|
|
45
|
+
`chart create` 已经不会空白。你只在想**限定时间范围**时才需要自己写 `filter`:
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
"filter": {
|
|
49
|
+
"filterRangeId": "ctime", // 按哪个日期字段限定;"ctime"=创建时间
|
|
50
|
+
"filterRangeName": "创建时间",
|
|
51
|
+
"rangeType": 0, // 0=全部时间;8=本月;18=过去N天(配 rangeValue)
|
|
52
|
+
"rangeValue": null,
|
|
53
|
+
"today": false
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`rangeType` 常用值:`0` 全部、`4` 本周、`8` 本月、`11` 本季度、`15` 本年、
|
|
58
|
+
`16` 上一年、`18` 今天之前 N 天(`rangeValue` 给天数,如 365)。
|
|
59
|
+
|
|
60
|
+
## 4. 创建:`hap worksheet chart create`
|
|
61
|
+
|
|
62
|
+
规格用 `-j/--spec-json`(内联)或 `-f/--spec-file`(文件)。字段引用一律用 **controlId**,
|
|
63
|
+
取字段用 `hap worksheet fields WS_ID`。
|
|
64
|
+
|
|
65
|
+
**柱图(按状态统计金额):**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
hap worksheet chart create WS_ID --name "各状态金额" --report-type 1 -j '{
|
|
69
|
+
"xaxes": {"controlId": "<状态字段>", "controlType": 11},
|
|
70
|
+
"yaxisList": [{"controlId": "<金额字段>", "controlType": 6, "normType": 1}]
|
|
71
|
+
}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**数值图(无维度,只放一个值):** `--report-type 10`,规格只给 `yaxisList`。
|
|
75
|
+
|
|
76
|
+
`yaxisList[].normType` 聚合方式:`1` 求和、`2` 最大、`3` 最小、`4` 平均、`5` 计数、
|
|
77
|
+
`6` 去重计数。数值字段默认 `1`,文本/选项字段用 `5`。记录数量用特殊列
|
|
78
|
+
`{"controlId": "record_count", "controlName": "记录数量", "controlType": 10000000, "normType": 5}`。
|
|
79
|
+
|
|
80
|
+
**日期维度**在 `xaxes` 里加 `particleSizeType`:`5` 年、`4` 季、`3` 月、`2` 周、`1` 日。
|
|
81
|
+
|
|
82
|
+
**带条件筛选**(不是时间范围,是「状态=已完成」这类):在 `filter` 里放 `items`
|
|
83
|
+
高层条件数组,CLI 会自动存成筛选器并回填 id:
|
|
84
|
+
|
|
85
|
+
```jsonc
|
|
86
|
+
"filter": {"items": [{"controlId": "<状态字段>", "dataType": 11, "spliceType": 1,
|
|
87
|
+
"filterType": 2, "values": ["<选项key>"]}]}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
用 `items` 时给 `--app-id 应用ID`(应用,不是工作表),CLI 才能存筛选器。
|
|
91
|
+
|
|
92
|
+
## 5. 修改:先取原配置,改几个参数,再提交
|
|
93
|
+
|
|
94
|
+
改图不用从头拼规格,**把原图配置读出来、改动要改的、整体提交回去**:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 1. 取出当前配置
|
|
98
|
+
hap --json worksheet chart get REPORT_ID --app-id WS_ID > chart.json
|
|
99
|
+
# 2. 编辑 chart.json(改 name / normType / xaxes / 加 filter 等)
|
|
100
|
+
# 3. 提交回去(update 必须带 --app-id=工作表ID、--name、--report-type)
|
|
101
|
+
hap worksheet chart update REPORT_ID --app-id WS_ID --name "新名字" --report-type 1 -f chart.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`get` 返回的 `controls` / `account` / `createdDate` 等多余字段可以原样带回,服务端会忽略。
|
|
105
|
+
`update` 会整体替换这张图的配置,所以要在原配置基础上改,别只发你改的那几个字段。
|
|
106
|
+
|
|
107
|
+
## 6. 放进自定义页(看板)
|
|
108
|
+
|
|
109
|
+
图建好后拿到 `reportId`,再用 `custom-page save` 把它作为一个组件放到页面上。
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 页面版本号:先 hap --json custom-page info PAGE_ID 看当前 version,save 时传相同值
|
|
113
|
+
hap custom-page save PAGE_ID --version <当前version> --components '[
|
|
114
|
+
{"type": 1, "value": "<reportId>", "worksheetId": "<WS_ID>",
|
|
115
|
+
"name": "各状态金额", "reportType": 1,
|
|
116
|
+
"config": {"objectId": "<32位十六进制随机串>"},
|
|
117
|
+
"web": {"title": "", "titleVisible": false, "visible": true,
|
|
118
|
+
"layout": {"x": 0, "y": 0, "w": 24, "h": 10, "minW": 2, "minH": 4}},
|
|
119
|
+
"mobile": {"title": "", "titleVisible": false, "visible": true, "layout": null}}
|
|
120
|
+
]'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
要点:
|
|
124
|
+
|
|
125
|
+
- 图表组件 `type` 是 **1**,`value` 放 `reportId`,还要带 `worksheetId` 和 `reportType`。
|
|
126
|
+
- `config.objectId` 每个组件一个唯一 32 位十六进制串(联动筛选靠它定位)。
|
|
127
|
+
- 布局是 **48 栅格**:`w` 最大 48,一行放两张图各 `w:24`。
|
|
128
|
+
- `--version` 必须等于页面当前 version,否则报「自定义页面保存失败」;存成功后 version +1。
|
|
129
|
+
- `save` 是**整页替换**:要保留页上已有组件,先 `custom-page info` 读出 `components`
|
|
130
|
+
再把新组件追加进去一起提交。
|
|
131
|
+
|
|
132
|
+
整体建应用带看板时优先用 `hap-cli-app-creator` skill,它按友好配置自动生成完整图表规格
|
|
133
|
+
和页面布局,比手写规格省心。手改单张图、或往已有页加一张图时,用上面的命令即可。
|
|
@@ -3930,8 +3930,8 @@
|
|
|
3930
3930
|
"zh-Hans": "工作表统计图管理"
|
|
3931
3931
|
},
|
|
3932
3932
|
"worksheet chart create": {
|
|
3933
|
-
"en": "Create a new chart on a worksheet.",
|
|
3934
|
-
"zh-Hans": "
|
|
3933
|
+
"en": "Create a new chart on a worksheet. Chart spec shapes are in `hap guide chart`.",
|
|
3934
|
+
"zh-Hans": "在工作表上创建新的统计图。图表规格的写法见 `hap guide chart`。"
|
|
3935
3935
|
},
|
|
3936
3936
|
"worksheet chart create --app-id": {
|
|
3937
3937
|
"en": "Application id the worksheet lives under. Required only when the chart spec carries inline filter conditions (filter.items) — the CLI then mints a stored filter on the user's behalf. Auto-resolved via the worksheet info when omitted.",
|
|
@@ -4006,8 +4006,8 @@
|
|
|
4006
4006
|
"zh-Hans": "每页条数(可选)"
|
|
4007
4007
|
},
|
|
4008
4008
|
"worksheet chart update": {
|
|
4009
|
-
"en": "Update an existing chart's configuration.",
|
|
4010
|
-
"zh-Hans": "
|
|
4009
|
+
"en": "Update an existing chart's configuration. Read the config with `chart get`, edit it, submit it back — see `hap guide chart`.",
|
|
4010
|
+
"zh-Hans": "更新已有统计图的配置。用 `chart get` 读出配置、修改后整体提交回去——见 `hap guide chart`。"
|
|
4011
4011
|
},
|
|
4012
4012
|
"worksheet chart update --app-id": {
|
|
4013
4013
|
"en": "Worksheet ID the chart attaches to.",
|
|
@@ -4245,6 +4245,10 @@
|
|
|
4245
4245
|
"en": "Application ID (auto-detected from the worksheet if omitted)",
|
|
4246
4246
|
"zh-Hans": "应用 ID(省略时自动从工作表检测)"
|
|
4247
4247
|
},
|
|
4248
|
+
"worksheet record add-discussion --file": {
|
|
4249
|
+
"en": "Attach a file to the comment (repeatable). Accepts a local path or an http(s) URL.",
|
|
4250
|
+
"zh-Hans": "为评论附加文件(可重复)。接受本地路径或 http(s) URL。"
|
|
4251
|
+
},
|
|
4248
4252
|
"worksheet record add-discussion --message": {
|
|
4249
4253
|
"en": "Discussion message",
|
|
4250
4254
|
"zh-Hans": "讨论内容"
|
|
@@ -4814,4 +4818,4 @@
|
|
|
4814
4818
|
"zh-Hans": "应用到现有视图的高层视图 JSON(格式与 'view create --view-spec' 相同)。规格中表达的内容全部更新;其他设置保持不变。字段引用可为名称、别名或 controlId。提供时忽略其他选项"
|
|
4815
4819
|
}
|
|
4816
4820
|
}
|
|
4817
|
-
}
|
|
4821
|
+
}
|
|
@@ -199,6 +199,39 @@ class TestSaveChart:
|
|
|
199
199
|
|
|
200
200
|
assert "splitId" not in mock_post.call_args[1]["json"]
|
|
201
201
|
|
|
202
|
+
@patch("hap_cli.core.session.requests.post")
|
|
203
|
+
def test_default_filter_block_injected_when_absent(self, mock_post):
|
|
204
|
+
# A chart saved without a `filter` block saves fine but never
|
|
205
|
+
# renders (blank after save). save_chart must inject an all-time
|
|
206
|
+
# default filter so a hand-written spec with only axes still works.
|
|
207
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
208
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
209
|
+
|
|
210
|
+
chart_mod.save_chart(
|
|
211
|
+
sess, app_id="ws1", name="Bare", report_type=1,
|
|
212
|
+
extra={"yaxisList": [{"controlId": "m1", "normType": 1}]},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
flt = mock_post.call_args[1]["json"]["filter"]
|
|
216
|
+
assert flt["filterRangeId"] == "ctime"
|
|
217
|
+
assert flt["rangeType"] == 0 # all-time, never blank by default
|
|
218
|
+
assert "dynamicFilter" in flt
|
|
219
|
+
|
|
220
|
+
@patch("hap_cli.core.session.requests.post")
|
|
221
|
+
def test_caller_supplied_filter_is_not_overwritten(self, mock_post):
|
|
222
|
+
# An explicit filter scope always wins over the injected default.
|
|
223
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
224
|
+
mock_post.return_value = _resp({"reportId": "r1"})
|
|
225
|
+
|
|
226
|
+
chart_mod.save_chart(
|
|
227
|
+
sess, app_id="ws1", name="Scoped", report_type=1,
|
|
228
|
+
extra={"filter": {"filterRangeId": "6a1", "rangeType": 8}},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
flt = mock_post.call_args[1]["json"]["filter"]
|
|
232
|
+
assert flt["filterRangeId"] == "6a1"
|
|
233
|
+
assert flt["rangeType"] == 8
|
|
234
|
+
|
|
202
235
|
@patch("hap_cli.core.session.requests.post")
|
|
203
236
|
def test_extra_dict_is_merged_under_required_fields(self, mock_post):
|
|
204
237
|
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
@@ -10042,6 +10042,30 @@ class TestRecordDiscussions:
|
|
|
10042
10042
|
with pytest.raises(APIError):
|
|
10043
10043
|
rec_mod.add_discussion(session, "ws1", "row1", "hi", app_id="app1")
|
|
10044
10044
|
|
|
10045
|
+
def test_add_discussion_encodes_attachments_as_json_string(self):
|
|
10046
|
+
# The frontend sends attachments as a JSON-encoded string under
|
|
10047
|
+
# `attachments`; a caller passing uploaded-file descriptors must
|
|
10048
|
+
# get that exact wire shape (and no key when none are supplied).
|
|
10049
|
+
session = MagicMock()
|
|
10050
|
+
session.get_global_meta.return_value = self._META
|
|
10051
|
+
session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
|
|
10052
|
+
descriptor = {"fileID": "f1", "originalFileName": "shot", "fileExt": ".png"}
|
|
10053
|
+
rec_mod.add_discussion(
|
|
10054
|
+
session, "ws1", "row1", "see attached", app_id="app1",
|
|
10055
|
+
attachments=[descriptor],
|
|
10056
|
+
)
|
|
10057
|
+
_, _, payload = session.api_call.call_args[0][:3]
|
|
10058
|
+
assert isinstance(payload["attachments"], str)
|
|
10059
|
+
assert json.loads(payload["attachments"]) == [descriptor]
|
|
10060
|
+
|
|
10061
|
+
def test_add_discussion_omits_attachments_when_none(self):
|
|
10062
|
+
session = MagicMock()
|
|
10063
|
+
session.get_global_meta.return_value = self._META
|
|
10064
|
+
session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
|
|
10065
|
+
rec_mod.add_discussion(session, "ws1", "row1", "no files", app_id="app1")
|
|
10066
|
+
_, _, payload = session.api_call.call_args[0][:3]
|
|
10067
|
+
assert "attachments" not in payload
|
|
10068
|
+
|
|
10045
10069
|
def test_remove_discussion_defaults_to_row_source_type(self):
|
|
10046
10070
|
session = MagicMock()
|
|
10047
10071
|
session.api_call.return_value = {"code": 1}
|
|
@@ -146,11 +146,13 @@ hap_cli/core/v3/dispatcher.py
|
|
|
146
146
|
hap_cli/core/v3/render.py
|
|
147
147
|
hap_cli/core/v3/schema.py
|
|
148
148
|
hap_cli/guide/index.json
|
|
149
|
+
hap_cli/guide/en/chart.md
|
|
149
150
|
hap_cli/guide/en/messages.md
|
|
150
151
|
hap_cli/guide/en/overview.md
|
|
151
152
|
hap_cli/guide/en/record.md
|
|
152
153
|
hap_cli/guide/en/setup.md
|
|
153
154
|
hap_cli/guide/en/skills.md
|
|
155
|
+
hap_cli/guide/zh-Hans/chart.md
|
|
154
156
|
hap_cli/guide/zh-Hans/messages.md
|
|
155
157
|
hap_cli/guide/zh-Hans/overview.md
|
|
156
158
|
hap_cli/guide/zh-Hans/record.md
|
|
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
|
|
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
|
|
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
|