hap-cli 0.8.29__tar.gz → 0.8.31__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.29 → hap_cli-0.8.31}/PKG-INFO +2 -1
- {hap_cli-0.8.29/hap_cli → hap_cli-0.8.31}/README.md +1 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31/hap_cli}/README.md +1 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/README_CN.md +1 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/calendar_cmd.py +36 -1
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/executor.py +6 -12
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/calendar_mod.py +65 -1
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/global_meta.py +15 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/record.py +8 -3
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/upload.py +25 -1
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/messages.json +17 -1
- hap_cli-0.8.31/hap_cli/tests/test_calendar_discussion_cli.py +89 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_core.py +75 -0
- hap_cli-0.8.31/hap_cli/tests/test_upload_allow_down.py +118 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/PKG-INFO +2 -1
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/SOURCES.txt +2 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/MANIFEST.in +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/context.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/approval.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/post.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/index.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/approval.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/post.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chat_recall_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_post_scope_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_switch.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/charts.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/config.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/errors.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/executor.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/fields.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/hap.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/schema.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/step.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/steps.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/store.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/setup.cfg +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/setup.py +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hap-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.31
|
|
4
4
|
Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
|
|
5
5
|
Author: hap-cli
|
|
6
6
|
License: Apache-2.0
|
|
@@ -264,6 +264,7 @@ discover every subcommand.
|
|
|
264
264
|
| `calendar list / get / create / update / delete` | Event lifecycle |
|
|
265
265
|
| `calendar categories` | Built-in categories |
|
|
266
266
|
| `calendar add-member / remove-member` | Membership |
|
|
267
|
+
| `calendar add-discussion` | Comment on an event (attachments supported) |
|
|
267
268
|
|
|
268
269
|
### chat — chat & messaging
|
|
269
270
|
|
|
@@ -235,6 +235,7 @@ discover every subcommand.
|
|
|
235
235
|
| `calendar list / get / create / update / delete` | Event lifecycle |
|
|
236
236
|
| `calendar categories` | Built-in categories |
|
|
237
237
|
| `calendar add-member / remove-member` | Membership |
|
|
238
|
+
| `calendar add-discussion` | Comment on an event (attachments supported) |
|
|
238
239
|
|
|
239
240
|
### chat — chat & messaging
|
|
240
241
|
|
|
@@ -235,6 +235,7 @@ discover every subcommand.
|
|
|
235
235
|
| `calendar list / get / create / update / delete` | Event lifecycle |
|
|
236
236
|
| `calendar categories` | Built-in categories |
|
|
237
237
|
| `calendar add-member / remove-member` | Membership |
|
|
238
|
+
| `calendar add-discussion` | Comment on an event (attachments supported) |
|
|
238
239
|
|
|
239
240
|
### chat — chat & messaging
|
|
240
241
|
|
|
@@ -218,6 +218,7 @@ hap --json worksheet record list WORKSHEET_ID
|
|
|
218
218
|
| `calendar list / get / create / update / delete` | 日程生命周期 |
|
|
219
219
|
| `calendar categories` | 内置分类 |
|
|
220
220
|
| `calendar add-member / remove-member` | 成员管理 |
|
|
221
|
+
| `calendar add-discussion` | 在日程下发表评论(可带附件) |
|
|
221
222
|
|
|
222
223
|
### chat — 聊天与消息
|
|
223
224
|
|
|
@@ -543,4 +543,39 @@ def calendar_categories(ctx):
|
|
|
543
543
|
summary = _simplify_categories_result(raw)
|
|
544
544
|
ctx.output(summary, _render_categories)
|
|
545
545
|
except Exception as e:
|
|
546
|
-
ctx.handle_error(e)
|
|
546
|
+
ctx.handle_error(e)
|
|
547
|
+
|
|
548
|
+
@calendar.command("add-discussion")
|
|
549
|
+
@click.argument("calendar_id")
|
|
550
|
+
@click.option("--message", "-m", required=True, help="Comment text")
|
|
551
|
+
@click.option("--reply-id", default="", help="ID of the comment being replied to")
|
|
552
|
+
@click.option("--file", "-f", "files", multiple=True,
|
|
553
|
+
help="Attach a file to the comment (repeatable). Accepts a local "
|
|
554
|
+
"path or an http(s) URL.")
|
|
555
|
+
@pass_context
|
|
556
|
+
def calendar_add_discussion(ctx, calendar_id, message, reply_id, files):
|
|
557
|
+
"""Post a comment on a calendar event.
|
|
558
|
+
|
|
559
|
+
Everyone invited to the event can see the comment. Attached files are
|
|
560
|
+
downloadable by every participant.
|
|
561
|
+
"""
|
|
562
|
+
try:
|
|
563
|
+
session = ctx.get_session()
|
|
564
|
+
attachments = None
|
|
565
|
+
if files:
|
|
566
|
+
from hap_cli.core import upload as up_mod
|
|
567
|
+
attachments = up_mod.upload_files(
|
|
568
|
+
session, up_mod.source_descriptors(list(files)))
|
|
569
|
+
raw = cal_mod.add_discussion(
|
|
570
|
+
session, calendar_id, message,
|
|
571
|
+
reply_id=reply_id, attachments=attachments,
|
|
572
|
+
)
|
|
573
|
+
data = raw.get("data") if isinstance(raw, dict) else None
|
|
574
|
+
summary = {
|
|
575
|
+
"success": True,
|
|
576
|
+
"discussion_id": (data or {}).get("discussionId", ""),
|
|
577
|
+
"calendar_id": calendar_id,
|
|
578
|
+
}
|
|
579
|
+
ctx.output(summary, lambda d: click.echo("Comment posted."))
|
|
580
|
+
except Exception as e:
|
|
581
|
+
ctx.handle_error(e)
|
|
@@ -141,18 +141,12 @@ class _Resolver:
|
|
|
141
141
|
|
|
142
142
|
def _record_attachment_cell(descriptors: list[dict[str, Any]]) -> dict[str, Any]:
|
|
143
143
|
"""Wrap general uploaded-file descriptors into a worksheet Attachment
|
|
144
|
-
(type 14) cell value
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"fileName": d.get("fileName"), "fileExt": d.get("fileExt"),
|
|
151
|
-
"originalFileName": d.get("originalFileName"), "key": d.get("key"),
|
|
152
|
-
"url": d.get("url"), "oldOriginalFileName": d.get("originalFileName"),
|
|
153
|
-
"index": i, "isEdit": False,
|
|
154
|
-
})
|
|
155
|
-
return {"attachmentData": [], "attachments": attachments, "knowledgeAtts": []}
|
|
144
|
+
(type 14) cell value. Delegates to the core wrapper so the seeded rows
|
|
145
|
+
carry exactly what any other CLI upload does — the local copy this
|
|
146
|
+
replaced silently dropped each file's download permission."""
|
|
147
|
+
from hap_cli.core.upload import record_attachment_cell
|
|
148
|
+
|
|
149
|
+
return record_attachment_cell(descriptors)
|
|
156
150
|
|
|
157
151
|
|
|
158
152
|
def _upload_row_attachments(
|
|
@@ -13,9 +13,12 @@ Enum re-exports (`FREQUENCY_*`, `REMIND_TYPE_*`) mirror
|
|
|
13
13
|
`src/pages/calendar/modules/calendarDetail/constant.js`.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import json
|
|
16
17
|
from typing import Any, Optional
|
|
17
18
|
|
|
18
|
-
from hap_cli.core
|
|
19
|
+
from hap_cli.core import global_meta as gm
|
|
20
|
+
from hap_cli.core import record as rec_mod
|
|
21
|
+
from hap_cli.core.session import Session, SessionError
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
# ── Enums (pd-openweb constants) ─────────────────────────────────────────
|
|
@@ -327,3 +330,64 @@ def remove_member(
|
|
|
327
330
|
def get_categories(session: Session) -> dict[str, Any]:
|
|
328
331
|
"""Get all calendar categories (`Calendar.GetUserAllCalCategories`)."""
|
|
329
332
|
return session.api_call("Calendar", "GetUserAllCalCategories", {})
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ── Discussions (comments on an event) ────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def add_discussion(
|
|
339
|
+
session: Session,
|
|
340
|
+
calendar_id: str,
|
|
341
|
+
message: str,
|
|
342
|
+
reply_id: str = "",
|
|
343
|
+
attachments: Optional[list[dict[str, Any]]] = None,
|
|
344
|
+
) -> dict[str, Any]:
|
|
345
|
+
"""Post a comment on a calendar event (`Discussion.AddDiscussion`).
|
|
346
|
+
|
|
347
|
+
Traced from `src/pages/calendar/modules/calendarDetail/container/
|
|
348
|
+
CalendarCommenter.jsx`, which configures the shared commenter
|
|
349
|
+
(`src/components/comment/commenter.jsx`) with:
|
|
350
|
+
|
|
351
|
+
- `sourceType` = SOURCE_TYPE.CALENDAR (3, comment/config.js)
|
|
352
|
+
- `sourceId` = the bare calendar id (no composite form, unlike the
|
|
353
|
+
worksheet-row case's ``{worksheetId}|{rowId}``)
|
|
354
|
+
- `appId` = md.global.APPInfo.calendarAppID, and *no*
|
|
355
|
+
`fromAppId` — a calendar event has no owning app
|
|
356
|
+
|
|
357
|
+
Two props of CalendarCommenter deliberately do **not** reach this
|
|
358
|
+
payload. `remark` is only consumed when the comment is additionally
|
|
359
|
+
shared to a post (`postAjax.addPost`), and `AddDiscussionRequest`
|
|
360
|
+
(sources/main_api/schemas/…AddDiscussionRequest.json) has no such
|
|
361
|
+
field. `extendsId` is documented "扩展ID(工作表:appId|viewId)" and the
|
|
362
|
+
calendar commenter never sets it.
|
|
363
|
+
|
|
364
|
+
``attachments`` carries uploaded-file descriptors from
|
|
365
|
+
``hap_cli.core.upload``; the frontend sends them JSON-encoded, and
|
|
366
|
+
each descriptor's ``allowDown`` is what lets anyone but the uploader
|
|
367
|
+
download the file (see upload._download_permission).
|
|
368
|
+
"""
|
|
369
|
+
app_id = gm.calendar_app_id(session.get_global_meta())
|
|
370
|
+
if not app_id:
|
|
371
|
+
# Guessing an app id here would produce a discussion the calendar
|
|
372
|
+
# UI cannot render, which is worse than refusing.
|
|
373
|
+
raise SessionError(
|
|
374
|
+
"This server did not publish the calendar application, so the "
|
|
375
|
+
"comment cannot be posted. Check that you are signed in to the "
|
|
376
|
+
"right server, then try again."
|
|
377
|
+
)
|
|
378
|
+
data: dict[str, Any] = {
|
|
379
|
+
"sourceId": calendar_id,
|
|
380
|
+
"sourceType": rec_mod.DISCUSSION_SOURCE_CALENDAR,
|
|
381
|
+
"message": message,
|
|
382
|
+
"appId": app_id,
|
|
383
|
+
# commenter.jsx: `forReacordDiscussion && entityType === 2 ? 2 : 0`
|
|
384
|
+
# — a calendar comment is always the plain kind.
|
|
385
|
+
"entityType": 0,
|
|
386
|
+
}
|
|
387
|
+
if reply_id:
|
|
388
|
+
data["replyId"] = reply_id
|
|
389
|
+
if attachments:
|
|
390
|
+
data["attachments"] = json.dumps(attachments, ensure_ascii=False)
|
|
391
|
+
result = session.api_call("Discussion", "AddDiscussion", data)
|
|
392
|
+
rec_mod.ensure_discussion_ok(result, "add comment")
|
|
393
|
+
return result
|
|
@@ -156,6 +156,21 @@ def worksheet_row_app_id(meta: Any) -> str:
|
|
|
156
156
|
return str(_app_info(meta).get("worksheetRowAppID") or "")
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
def calendar_app_id(meta: Any) -> str:
|
|
160
|
+
"""Return ``md.global.APPInfo.calendarAppID``.
|
|
161
|
+
|
|
162
|
+
The internal "application" id HAP uses for **calendar-event**
|
|
163
|
+
discussions (``sourceType`` 3 / ``Commenter.TYPES.CALENDAR`` in
|
|
164
|
+
pd-openweb ``src/components/comment/config.js``). ``AddDiscussion``
|
|
165
|
+
must carry it as ``appId`` — see
|
|
166
|
+
``src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx``.
|
|
167
|
+
Unlike the worksheet-row case there is no separate ``fromAppId``:
|
|
168
|
+
a calendar event has no owning app. Returns empty string when the
|
|
169
|
+
deployment does not expose it.
|
|
170
|
+
"""
|
|
171
|
+
return str(_app_info(meta).get("calendarAppID") or "")
|
|
172
|
+
|
|
173
|
+
|
|
159
174
|
def worksheet_app_id(meta: Any) -> str:
|
|
160
175
|
"""Return ``md.global.APPInfo.worksheetAppID`` (whole-worksheet
|
|
161
176
|
discussions, ``sourceType`` 7). Empty string when absent."""
|
|
@@ -12,10 +12,15 @@ from hap_cli.core import global_meta as gm
|
|
|
12
12
|
logger = logging.getLogger("hap_cli.record")
|
|
13
13
|
|
|
14
14
|
# Discussion source types — pd-openweb src/components/comment/config.js
|
|
15
|
+
# CALENDAR = 3 calendar-event discussion
|
|
15
16
|
# WORKSHEET = 7 whole-worksheet discussion
|
|
16
17
|
# WORKSHEETROW = 8 single record/row discussion
|
|
17
18
|
# Record-scoped discussions (add/get/delete on a row) are always type 8,
|
|
18
19
|
# and their sourceId is "{worksheetId}|{rowId}" — NOT the bare rowId.
|
|
20
|
+
# The whole enum lives here because this module owns the Discussion
|
|
21
|
+
# endpoint primitives; other modules (calendar) import from it rather
|
|
22
|
+
# than redeclaring their own copy.
|
|
23
|
+
DISCUSSION_SOURCE_CALENDAR = 3
|
|
19
24
|
DISCUSSION_SOURCE_WORKSHEET = 7
|
|
20
25
|
DISCUSSION_SOURCE_WORKSHEET_ROW = 8
|
|
21
26
|
|
|
@@ -1050,7 +1055,7 @@ def _discussion_source_id(worksheet_id: str, row_id: str) -> str:
|
|
|
1050
1055
|
return f"{worksheet_id}|{row_id}"
|
|
1051
1056
|
|
|
1052
1057
|
|
|
1053
|
-
def
|
|
1058
|
+
def ensure_discussion_ok(result: Any, action: str) -> None:
|
|
1054
1059
|
"""Raise if the Discussion endpoint reported failure.
|
|
1055
1060
|
|
|
1056
1061
|
Discussion endpoints return ``{code: 1, data: ...}`` on success and a
|
|
@@ -1138,7 +1143,7 @@ def add_discussion(
|
|
|
1138
1143
|
if attachments:
|
|
1139
1144
|
data["attachments"] = json.dumps(attachments, ensure_ascii=False)
|
|
1140
1145
|
result = session.api_call("Discussion", "AddDiscussion", data)
|
|
1141
|
-
|
|
1146
|
+
ensure_discussion_ok(result, "add discussion")
|
|
1142
1147
|
return result
|
|
1143
1148
|
|
|
1144
1149
|
|
|
@@ -1157,7 +1162,7 @@ def remove_discussion(
|
|
|
1157
1162
|
"Discussion", "RemoveDiscussion",
|
|
1158
1163
|
{"discussionId": discussion_id, "sourceType": source_type},
|
|
1159
1164
|
)
|
|
1160
|
-
|
|
1165
|
+
ensure_discussion_ok(result, "delete discussion")
|
|
1161
1166
|
return result
|
|
1162
1167
|
|
|
1163
1168
|
|
|
@@ -209,6 +209,23 @@ def _iso(deadline: Optional[int]) -> Optional[str]:
|
|
|
209
209
|
return datetime.fromtimestamp(deadline, tz=timezone.utc).astimezone().isoformat()
|
|
210
210
|
|
|
211
211
|
|
|
212
|
+
def _download_permission(ext: str) -> dict[str, Any]:
|
|
213
|
+
"""The per-file download permission the holder of the file is sent.
|
|
214
|
+
|
|
215
|
+
Whoever stores an attachment decides, per file, whether anyone but
|
|
216
|
+
the uploader may download it — an omitted flag used to read as "no",
|
|
217
|
+
so a file attached by the CLI answered 权限不足 for every colleague.
|
|
218
|
+
pd-openweb sets it in ``formatResponseData``
|
|
219
|
+
(src/components/UploadFiles/utils.js:202-206): non-pictures get
|
|
220
|
+
``allowDown: true`` plus an empty ``docVersionID``, pictures get
|
|
221
|
+
neither because the viewer already treats a picture as downloadable
|
|
222
|
+
(UploadFiles/File.js:724). Mirrored verbatim, empty dict included.
|
|
223
|
+
"""
|
|
224
|
+
if ext.lower() in _PICTURE_EXTS:
|
|
225
|
+
return {}
|
|
226
|
+
return {"allowDown": True, "docVersionID": ""}
|
|
227
|
+
|
|
228
|
+
|
|
212
229
|
def upload_bytes(
|
|
213
230
|
session: Session,
|
|
214
231
|
*,
|
|
@@ -239,7 +256,7 @@ def upload_bytes(
|
|
|
239
256
|
up = _post_to_store(upload_host(session), token, content,
|
|
240
257
|
original_name=original, ext=ext,
|
|
241
258
|
server_name=server_name)
|
|
242
|
-
|
|
259
|
+
descriptor = {
|
|
243
260
|
"fileID": generate_file_oid(),
|
|
244
261
|
"fileSize": len(content),
|
|
245
262
|
"etag": up.get("etag"),
|
|
@@ -256,6 +273,8 @@ def upload_bytes(
|
|
|
256
273
|
# (public-bucket) address, which does not expire.
|
|
257
274
|
"urlExpiresAt": _iso(url_expires_at(token.get("url"))),
|
|
258
275
|
}
|
|
276
|
+
descriptor.update(_download_permission(ext))
|
|
277
|
+
return descriptor
|
|
259
278
|
|
|
260
279
|
|
|
261
280
|
def source_descriptors(sources: "list[str] | tuple[str, ...]") -> list[dict[str, str]]:
|
|
@@ -324,4 +343,9 @@ def record_attachment_cell(descriptors: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
324
343
|
"index": i,
|
|
325
344
|
"isEdit": False,
|
|
326
345
|
})
|
|
346
|
+
# Carried, not recomputed: the descriptor already decided this at
|
|
347
|
+
# upload time — see _download_permission().
|
|
348
|
+
for key in ("allowDown", "docVersionID"):
|
|
349
|
+
if key in d:
|
|
350
|
+
attachments[-1][key] = d[key]
|
|
327
351
|
return {"attachmentData": [], "attachments": attachments, "knowledgeAtts": []}
|
|
@@ -2217,6 +2217,22 @@
|
|
|
2217
2217
|
"en": "Calendar and schedule management.",
|
|
2218
2218
|
"zh-Hans": "日程与日历管理"
|
|
2219
2219
|
},
|
|
2220
|
+
"calendar add-discussion": {
|
|
2221
|
+
"en": "Post a comment on a calendar event.\n\nEveryone invited to the event can see the comment. Attached files are\ndownloadable by every participant.",
|
|
2222
|
+
"zh-Hans": "在日程下发表评论。\n\n日程的所有参与者都能看到这条评论;随评论上传的附件,参与者均可下载。"
|
|
2223
|
+
},
|
|
2224
|
+
"calendar add-discussion --file": {
|
|
2225
|
+
"en": "Attach a file to the comment (repeatable). Accepts a local path or an http(s) URL.",
|
|
2226
|
+
"zh-Hans": "为评论添加附件(可重复指定)。可传本地路径或 http(s) 链接。"
|
|
2227
|
+
},
|
|
2228
|
+
"calendar add-discussion --message": {
|
|
2229
|
+
"en": "Comment text",
|
|
2230
|
+
"zh-Hans": "评论内容"
|
|
2231
|
+
},
|
|
2232
|
+
"calendar add-discussion --reply-id": {
|
|
2233
|
+
"en": "ID of the comment being replied to",
|
|
2234
|
+
"zh-Hans": "被回复的评论 ID"
|
|
2235
|
+
},
|
|
2220
2236
|
"calendar add-member": {
|
|
2221
2237
|
"en": "Add members to a calendar event.",
|
|
2222
2238
|
"zh-Hans": "向日程事件添加成员"
|
|
@@ -4890,4 +4906,4 @@
|
|
|
4890
4906
|
"zh-Hans": "应用到现有视图的高层视图 JSON(格式与 'view create --view-spec' 相同)。规格中表达的内容全部更新;其他设置保持不变。字段引用可为名称、别名或 controlId。提供时忽略其他选项"
|
|
4891
4907
|
}
|
|
4892
4908
|
}
|
|
4893
|
-
}
|
|
4909
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""`hap calendar add-discussion` — commenting on a calendar event.
|
|
2
|
+
|
|
3
|
+
Until this command existed, attaching a file to a calendar event's
|
|
4
|
+
comment thread had no CLI path at all: the only `Discussion/AddDiscussion`
|
|
5
|
+
caller in the codebase was the worksheet-record one, pinned to
|
|
6
|
+
`sourceType` 8. Anyone who needed it had to assemble the request by hand
|
|
7
|
+
against the raw session — which is how a calendar comment attachment
|
|
8
|
+
first shipped without its download permission (see
|
|
9
|
+
test_upload_allow_down.py).
|
|
10
|
+
|
|
11
|
+
Payload traced from pd-openweb
|
|
12
|
+
`src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx`
|
|
13
|
+
→ `src/components/comment/commenter.jsx`, cross-checked against
|
|
14
|
+
`sources/main_api/schemas/MD.Web.Ajax.Request.Discussion.AddDiscussionRequest.json`.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from unittest.mock import patch
|
|
18
|
+
|
|
19
|
+
from click.testing import CliRunner
|
|
20
|
+
|
|
21
|
+
from hap_cli.hap_cli import cli
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestAddDiscussionCli:
|
|
25
|
+
|
|
26
|
+
def test_message_reaches_the_core_call(self):
|
|
27
|
+
runner = CliRunner()
|
|
28
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
29
|
+
patch("hap_cli.core.calendar_mod.add_discussion",
|
|
30
|
+
return_value={"code": 1, "data": {"discussionId": "d1"}}) as m:
|
|
31
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
|
|
32
|
+
"-m", "会前看下资料"])
|
|
33
|
+
assert res.exit_code == 0, res.output
|
|
34
|
+
assert m.call_args[0][1:] == ("cal1", "会前看下资料")
|
|
35
|
+
assert m.call_args[1]["reply_id"] == ""
|
|
36
|
+
assert m.call_args[1]["attachments"] is None
|
|
37
|
+
|
|
38
|
+
def test_reply_id_is_forwarded(self):
|
|
39
|
+
runner = CliRunner()
|
|
40
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
41
|
+
patch("hap_cli.core.calendar_mod.add_discussion",
|
|
42
|
+
return_value={"code": 1, "data": {}}) as m:
|
|
43
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
|
|
44
|
+
"-m", "收到", "--reply-id", "d0"])
|
|
45
|
+
assert res.exit_code == 0, res.output
|
|
46
|
+
assert m.call_args[1]["reply_id"] == "d0"
|
|
47
|
+
|
|
48
|
+
def test_files_are_uploaded_and_attached(self, tmp_path):
|
|
49
|
+
runner = CliRunner()
|
|
50
|
+
f = tmp_path / "proto.zip"
|
|
51
|
+
f.write_bytes(b"zip")
|
|
52
|
+
descriptor = {"fileID": "o_1", "fileExt": ".zip", "allowDown": True}
|
|
53
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
54
|
+
patch("hap_cli.core.upload.upload_files",
|
|
55
|
+
return_value=[descriptor]) as up, \
|
|
56
|
+
patch("hap_cli.core.calendar_mod.add_discussion",
|
|
57
|
+
return_value={"code": 1, "data": {}}) as m:
|
|
58
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
|
|
59
|
+
"-m", "原型包", "-f", str(f)])
|
|
60
|
+
assert res.exit_code == 0, res.output
|
|
61
|
+
assert up.call_args[0][1] == [{"path": str(f)}]
|
|
62
|
+
assert m.call_args[1]["attachments"] == [descriptor]
|
|
63
|
+
|
|
64
|
+
def test_an_http_source_is_fetched_rather_than_opened_as_a_path(self):
|
|
65
|
+
runner = CliRunner()
|
|
66
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
67
|
+
patch("hap_cli.core.upload.upload_files",
|
|
68
|
+
return_value=[{"fileID": "o_2"}]) as up, \
|
|
69
|
+
patch("hap_cli.core.calendar_mod.add_discussion",
|
|
70
|
+
return_value={"code": 1, "data": {}}):
|
|
71
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
|
|
72
|
+
"-m", "链接", "-f",
|
|
73
|
+
"https://example.com/a.pdf"])
|
|
74
|
+
assert res.exit_code == 0, res.output
|
|
75
|
+
assert up.call_args[0][1] == [{"url": "https://example.com/a.pdf"}]
|
|
76
|
+
|
|
77
|
+
def test_message_is_required(self):
|
|
78
|
+
runner = CliRunner()
|
|
79
|
+
with patch("hap_cli.context.Context.get_session"):
|
|
80
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "cal1"])
|
|
81
|
+
assert res.exit_code != 0
|
|
82
|
+
|
|
83
|
+
def test_help_is_end_user_copy(self):
|
|
84
|
+
runner = CliRunner()
|
|
85
|
+
res = runner.invoke(cli, ["calendar", "add-discussion", "--help"])
|
|
86
|
+
assert res.exit_code == 0
|
|
87
|
+
for leak in ("AddDiscussion", "Discussion/", "sourceType", "pd-openweb",
|
|
88
|
+
"calendarAppID", "commenter", "endpoint"):
|
|
89
|
+
assert leak not in res.output
|
|
@@ -4022,6 +4022,81 @@ class TestCalendar:
|
|
|
4022
4022
|
assert body["isAllCalendar"] is True
|
|
4023
4023
|
|
|
4024
4024
|
|
|
4025
|
+
class TestCalendarDiscussions:
|
|
4026
|
+
"""Comments on a calendar event.
|
|
4027
|
+
|
|
4028
|
+
Traced from pd-openweb
|
|
4029
|
+
`src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx`,
|
|
4030
|
+
which feeds the shared `src/components/comment/commenter.jsx`:
|
|
4031
|
+
`sourceType` = SOURCE_TYPE.CALENDAR (3, config.js) and `appId` =
|
|
4032
|
+
`md.global.APPInfo.calendarAppID`. The commenter's `remark` prop is
|
|
4033
|
+
*not* part of the AddDiscussion payload — it is only used when the
|
|
4034
|
+
comment is additionally shared to a post (`postAjax.addPost`), and
|
|
4035
|
+
`AddDiscussionRequest` has no such field. `extendsId` is documented
|
|
4036
|
+
"扩展ID(工作表:appId|viewId)" and the calendar commenter never sets it.
|
|
4037
|
+
"""
|
|
4038
|
+
|
|
4039
|
+
_META = {"md.global": {"APPInfo": {"calendarAppID": "cal-app-id"}}}
|
|
4040
|
+
|
|
4041
|
+
def _session(self):
|
|
4042
|
+
session = MagicMock()
|
|
4043
|
+
session.get_global_meta.return_value = self._META
|
|
4044
|
+
session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
|
|
4045
|
+
return session
|
|
4046
|
+
|
|
4047
|
+
def test_payload_shape(self):
|
|
4048
|
+
from hap_cli.core import calendar_mod
|
|
4049
|
+
session = self._session()
|
|
4050
|
+
calendar_mod.add_discussion(session, "cal1", "看下这个附件")
|
|
4051
|
+
controller, action, payload = session.api_call.call_args[0][:3]
|
|
4052
|
+
assert (controller, action) == ("Discussion", "AddDiscussion")
|
|
4053
|
+
assert payload["sourceId"] == "cal1" # the bare calendar id
|
|
4054
|
+
assert payload["sourceType"] == 3 # SOURCE_TYPE.CALENDAR
|
|
4055
|
+
assert payload["appId"] == "cal-app-id" # APPInfo.calendarAppID
|
|
4056
|
+
assert payload["message"] == "看下这个附件"
|
|
4057
|
+
assert payload["entityType"] == 0
|
|
4058
|
+
# Worksheet-only / post-only fields must not leak in.
|
|
4059
|
+
assert "extendsId" not in payload
|
|
4060
|
+
assert "remark" not in payload
|
|
4061
|
+
assert "fromAppId" not in payload
|
|
4062
|
+
|
|
4063
|
+
def test_reply_id_is_sent_only_when_given(self):
|
|
4064
|
+
from hap_cli.core import calendar_mod
|
|
4065
|
+
session = self._session()
|
|
4066
|
+
calendar_mod.add_discussion(session, "cal1", "收到")
|
|
4067
|
+
assert "replyId" not in session.api_call.call_args[0][2]
|
|
4068
|
+
calendar_mod.add_discussion(session, "cal1", "收到", reply_id="d0")
|
|
4069
|
+
assert session.api_call.call_args[0][2]["replyId"] == "d0"
|
|
4070
|
+
|
|
4071
|
+
def test_attachments_are_json_encoded(self):
|
|
4072
|
+
from hap_cli.core import calendar_mod
|
|
4073
|
+
session = self._session()
|
|
4074
|
+
calendar_mod.add_discussion(
|
|
4075
|
+
session, "cal1", "原型包",
|
|
4076
|
+
attachments=[{"fileID": "o_1", "fileExt": ".zip", "allowDown": True}],
|
|
4077
|
+
)
|
|
4078
|
+
payload = session.api_call.call_args[0][2]
|
|
4079
|
+
sent = json.loads(payload["attachments"])
|
|
4080
|
+
assert sent[0]["fileID"] == "o_1"
|
|
4081
|
+
# The download permission must survive the hop — see
|
|
4082
|
+
# test_upload_allow_down.py for why it exists at all.
|
|
4083
|
+
assert sent[0]["allowDown"] is True
|
|
4084
|
+
|
|
4085
|
+
def test_failure_code_raises_instead_of_reporting_success(self):
|
|
4086
|
+
from hap_cli.core import calendar_mod
|
|
4087
|
+
session = self._session()
|
|
4088
|
+
session.api_call.return_value = {"code": 7} # 没有权限操作
|
|
4089
|
+
with pytest.raises(APIError):
|
|
4090
|
+
calendar_mod.add_discussion(session, "cal1", "hi")
|
|
4091
|
+
|
|
4092
|
+
def test_missing_calendar_app_id_is_an_error_not_a_guess(self):
|
|
4093
|
+
from hap_cli.core import calendar_mod
|
|
4094
|
+
session = self._session()
|
|
4095
|
+
session.get_global_meta.return_value = {"md.global": {"APPInfo": {}}}
|
|
4096
|
+
with pytest.raises(SessionError):
|
|
4097
|
+
calendar_mod.add_discussion(session, "cal1", "hi")
|
|
4098
|
+
|
|
4099
|
+
|
|
4025
4100
|
# ── Chat Tests ────────────────────────────────────────────────────────────
|
|
4026
4101
|
|
|
4027
4102
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Uploaded files must carry their download permission.
|
|
2
|
+
|
|
3
|
+
Reported as "日程/讨论里别人上传的文件下载不了": a colleague opening a file
|
|
4
|
+
attached through the CLI got *您权限不足,无法下载,请联系管理员或文件上传者*,
|
|
5
|
+
while the same file attached from the web UI downloaded fine.
|
|
6
|
+
|
|
7
|
+
The file store does not decide this — the *client* does, per file, when
|
|
8
|
+
it hands the upload result to whatever will hold it. pd-openweb does it
|
|
9
|
+
in ``formatResponseData`` (src/components/UploadFiles/utils.js:202-206):
|
|
10
|
+
every non-picture upload gets ``allowDown: true`` and an empty
|
|
11
|
+
``docVersionID``; pictures are left alone because the viewer treats them
|
|
12
|
+
as downloadable anyway (UploadFiles/File.js:724). Every attachment
|
|
13
|
+
payload the frontend sends — record comments, record attachment cells —
|
|
14
|
+
is that same object, stringified.
|
|
15
|
+
|
|
16
|
+
The CLI's uploaded-file descriptor never had the field, so anything it
|
|
17
|
+
attached was stored with the permission unset and only the uploader
|
|
18
|
+
could get the bytes back.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from unittest.mock import MagicMock, patch
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
from hap_cli.core import record as rec_mod
|
|
27
|
+
from hap_cli.core import upload as up_mod
|
|
28
|
+
from hap_cli.core.session import Session
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def session():
|
|
33
|
+
return Session(server_url="https://test.mingdao.com", auth_token="tok123")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _store:
|
|
37
|
+
"""Stand in for the credential + store round trip of an upload."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, ext=".pdf"):
|
|
40
|
+
self.ext = ext
|
|
41
|
+
|
|
42
|
+
def __enter__(self):
|
|
43
|
+
token = {"uptoken": "t", "key": f"doc/20260909/k{self.ext}",
|
|
44
|
+
"serverName": "https://doc.mingdao.com/",
|
|
45
|
+
"fileName": f"k{self.ext}",
|
|
46
|
+
"url": f"https://doc.mingdao.com/doc/k{self.ext}"}
|
|
47
|
+
self._patches = [
|
|
48
|
+
patch.object(up_mod, "get_upload_token", return_value=token),
|
|
49
|
+
patch.object(up_mod, "upload_host",
|
|
50
|
+
return_value="https://upload.qiniup.com/"),
|
|
51
|
+
patch.object(up_mod, "_post_to_store", return_value={
|
|
52
|
+
"key": f"doc/20260909/k{self.ext}", "etag": "e",
|
|
53
|
+
"bucket": "mdoc", "filePath": "doc/20260909/", "fileName": "k",
|
|
54
|
+
"fileExt": self.ext, "originalFileName": "spec",
|
|
55
|
+
}),
|
|
56
|
+
]
|
|
57
|
+
for p in self._patches:
|
|
58
|
+
p.start()
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def __exit__(self, *exc):
|
|
62
|
+
for p in self._patches:
|
|
63
|
+
p.stop()
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestDescriptorCarriesDownloadPermission:
|
|
68
|
+
|
|
69
|
+
def test_document_upload_is_downloadable(self, session):
|
|
70
|
+
with _store(".pdf"):
|
|
71
|
+
d = up_mod.upload_bytes(session, content=b"x", name="spec.pdf")
|
|
72
|
+
assert d["allowDown"] is True
|
|
73
|
+
assert d["docVersionID"] == ""
|
|
74
|
+
|
|
75
|
+
def test_archive_upload_is_downloadable(self, session):
|
|
76
|
+
# The reported file was a 13 MB .zip attached to a calendar comment.
|
|
77
|
+
with _store(".zip"):
|
|
78
|
+
d = up_mod.upload_bytes(session, content=b"x", name="proto.zip")
|
|
79
|
+
assert d["allowDown"] is True
|
|
80
|
+
|
|
81
|
+
def test_picture_upload_leaves_the_field_alone(self, session):
|
|
82
|
+
# Mirrors formatResponseData: pictures get neither field.
|
|
83
|
+
with _store(".png"):
|
|
84
|
+
d = up_mod.upload_bytes(session, content=b"x", name="cover.png")
|
|
85
|
+
assert "allowDown" not in d
|
|
86
|
+
assert "docVersionID" not in d
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TestConsumersKeepThePermission:
|
|
90
|
+
|
|
91
|
+
_META = {"md.global": {"APPInfo": {"worksheetRowAppID": "row-app-id"}}}
|
|
92
|
+
|
|
93
|
+
def test_record_comment_attachment_keeps_it(self):
|
|
94
|
+
session = MagicMock()
|
|
95
|
+
session.get_global_meta.return_value = self._META
|
|
96
|
+
session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
|
|
97
|
+
rec_mod.add_discussion(
|
|
98
|
+
session, "ws1", "row1", "here you go", app_id="app1",
|
|
99
|
+
attachments=[{"fileID": "o_1", "fileExt": ".zip", "allowDown": True,
|
|
100
|
+
"docVersionID": ""}],
|
|
101
|
+
)
|
|
102
|
+
_, _, payload = session.api_call.call_args[0][:3]
|
|
103
|
+
assert json.loads(payload["attachments"])[0]["allowDown"] is True
|
|
104
|
+
|
|
105
|
+
def test_attachment_cell_keeps_it(self):
|
|
106
|
+
cell = up_mod.record_attachment_cell([
|
|
107
|
+
{"fileID": "o_1", "fileExt": ".zip", "originalFileName": "proto",
|
|
108
|
+
"allowDown": True, "docVersionID": ""},
|
|
109
|
+
])
|
|
110
|
+
assert cell["attachments"][0]["allowDown"] is True
|
|
111
|
+
assert cell["attachments"][0]["docVersionID"] == ""
|
|
112
|
+
|
|
113
|
+
def test_attachment_cell_leaves_a_picture_alone(self):
|
|
114
|
+
cell = up_mod.record_attachment_cell([
|
|
115
|
+
{"fileID": "o_2", "fileExt": ".png", "originalFileName": "cover"},
|
|
116
|
+
])
|
|
117
|
+
assert "allowDown" not in cell["attachments"][0]
|
|
118
|
+
assert "docVersionID" not in cell["attachments"][0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hap-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.31
|
|
4
4
|
Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
|
|
5
5
|
Author: hap-cli
|
|
6
6
|
License: Apache-2.0
|
|
@@ -264,6 +264,7 @@ discover every subcommand.
|
|
|
264
264
|
| `calendar list / get / create / update / delete` | Event lifecycle |
|
|
265
265
|
| `calendar categories` | Built-in categories |
|
|
266
266
|
| `calendar add-member / remove-member` | Membership |
|
|
267
|
+
| `calendar add-discussion` | Comment on an event (attachments supported) |
|
|
267
268
|
|
|
268
269
|
### chat — chat & messaging
|
|
269
270
|
|