hap-cli 0.8.25__tar.gz → 0.8.27__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.
Files changed (289) hide show
  1. {hap_cli-0.8.25 → hap_cli-0.8.27}/PKG-INFO +1 -1
  2. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/__init__.py +1 -1
  3. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/chart_cmd.py +5 -2
  4. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/instance_cmd.py +102 -2
  5. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/record_cmd.py +25 -3
  6. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/chart.py +30 -0
  7. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/record.py +29 -9
  8. hap_cli-0.8.27/hap_cli/guide/en/approval.md +118 -0
  9. hap_cli-0.8.27/hap_cli/guide/en/chart.md +150 -0
  10. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/index.json +16 -0
  11. hap_cli-0.8.27/hap_cli/guide/zh-Hans/approval.md +105 -0
  12. hap_cli-0.8.27/hap_cli/guide/zh-Hans/chart.md +133 -0
  13. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/locale/messages.json +31 -7
  14. hap_cli-0.8.27/hap_cli/tests/test_approval_submit_fill_cli.py +143 -0
  15. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_chart.py +33 -0
  16. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_core.py +49 -0
  17. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/PKG-INFO +1 -1
  18. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/SOURCES.txt +5 -0
  19. {hap_cli-0.8.25 → hap_cli-0.8.27}/MANIFEST.in +0 -0
  20. {hap_cli-0.8.25 → hap_cli-0.8.27}/README.md +0 -0
  21. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/__init__.py +0 -0
  22. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/__main__.py +0 -0
  23. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/config.py +0 -0
  24. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/harness.py +0 -0
  25. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/smoke.py +0 -0
  26. {hap_cli-0.8.25 → hap_cli-0.8.27}/app_live_tests/state.py +0 -0
  27. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/README.md +0 -0
  28. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/README_CN.md +0 -0
  29. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/__main__.py +0 -0
  30. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/__init__.py +0 -0
  31. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/app_cmd.py +0 -0
  32. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/app_creator_cmd.py +0 -0
  33. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/app_editor_cmd.py +0 -0
  34. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/attachment_cmd.py +0 -0
  35. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/auth_cmd.py +0 -0
  36. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/calendar_cmd.py +0 -0
  37. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/chat_cmd.py +0 -0
  38. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/chatbot_cmd.py +0 -0
  39. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/config_cmd.py +0 -0
  40. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/contact_cmd.py +0 -0
  41. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/department_cmd.py +0 -0
  42. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/group_cmd.py +0 -0
  43. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/guide_cmd.py +0 -0
  44. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/icon_cmd.py +0 -0
  45. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/node_cmd.py +0 -0
  46. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/optionset_cmd.py +0 -0
  47. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/page_cmd.py +0 -0
  48. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/post_cmd.py +0 -0
  49. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/region_cmd.py +0 -0
  50. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/role_cmd.py +0 -0
  51. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/update_cmd.py +0 -0
  52. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/upload_cmd.py +0 -0
  53. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/v3_registry.py +0 -0
  54. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/workflow_cmd.py +0 -0
  55. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/commands/worksheet_cmd.py +0 -0
  56. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/context.py +0 -0
  57. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/__init__.py +0 -0
  58. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/action_spec_adapter.py +0 -0
  59. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app.py +0 -0
  60. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/__init__.py +0 -0
  61. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  62. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  63. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  64. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/charts.py +0 -0
  65. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/cleanup.py +0 -0
  66. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/compiler.py +0 -0
  67. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/config.py +0 -0
  68. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  69. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/errors.py +0 -0
  70. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  71. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/executor.py +0 -0
  72. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/fields.py +0 -0
  73. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/hap.py +0 -0
  74. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/merge.py +0 -0
  75. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  76. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/console.py +0 -0
  77. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  78. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  79. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/report.py +0 -0
  80. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/schema.py +0 -0
  81. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  82. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  83. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/cli.py +0 -0
  84. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/executor.py +0 -0
  85. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  86. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  87. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  88. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  89. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/template.py +0 -0
  90. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/selftest.py +0 -0
  91. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/smoke.py +0 -0
  92. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/steps.py +0 -0
  93. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/store.py +0 -0
  94. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/validate.py +0 -0
  95. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  96. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/attachment.py +0 -0
  97. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/auth.py +0 -0
  98. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/calendar_mod.py +0 -0
  99. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/chat.py +0 -0
  100. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/chatbot.py +0 -0
  101. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/cli_access.py +0 -0
  102. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/contact.py +0 -0
  103. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/control_type_codes.py +0 -0
  104. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/department.py +0 -0
  105. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/__init__.py +0 -0
  106. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  107. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  108. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/apply.py +0 -0
  109. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/componentlower.py +0 -0
  110. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  111. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  112. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  113. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  114. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/editspec.py +0 -0
  115. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/errors.py +0 -0
  116. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/fieldlower.py +0 -0
  117. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  118. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/models.py +0 -0
  119. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/ops.py +0 -0
  120. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/planner.py +0 -0
  121. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/editor/reader.py +0 -0
  122. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/field_normalizer.py +0 -0
  123. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/field_spec_adapter.py +0 -0
  124. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/filter_translator.py +0 -0
  125. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/flow_node.py +0 -0
  126. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/global_meta.py +0 -0
  127. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/group.py +0 -0
  128. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/guide.py +0 -0
  129. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/icon_index.py +0 -0
  130. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/instance.py +0 -0
  131. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/logger.py +0 -0
  132. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/optionset.py +0 -0
  133. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/page.py +0 -0
  134. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/post.py +0 -0
  135. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/response_crypto.py +0 -0
  136. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/role.py +0 -0
  137. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/role_perm_builder.py +0 -0
  138. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/session.py +0 -0
  139. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/token_crypto.py +0 -0
  140. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/update.py +0 -0
  141. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/upload.py +0 -0
  142. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/v3/__init__.py +0 -0
  143. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/v3/dispatcher.py +0 -0
  144. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/v3/render.py +0 -0
  145. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/v3/schema.py +0 -0
  146. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/view_spec_adapter.py +0 -0
  147. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/workflow.py +0 -0
  148. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/workflow_node_dsl.py +0 -0
  149. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/worksheet.py +0 -0
  150. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/core/worksheet_templates.py +0 -0
  151. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/en/messages.md +0 -0
  152. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/en/overview.md +0 -0
  153. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/en/record.md +0 -0
  154. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/en/setup.md +0 -0
  155. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/en/skills.md +0 -0
  156. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/messages.md +0 -0
  157. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/overview.md +0 -0
  158. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/record.md +0 -0
  159. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/setup.md +0 -0
  160. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/skills.md +0 -0
  161. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/hap_cli.py +0 -0
  162. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/i18n.py +0 -0
  163. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/locale/__init__.py +0 -0
  164. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/locale/messages.schema.json +0 -0
  165. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/__init__.py +0 -0
  166. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/conftest.py +0 -0
  167. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_app_creator.py +0 -0
  168. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_app_editor.py +0 -0
  169. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_app_export_usage.py +0 -0
  170. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_attachment_share.py +0 -0
  171. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_auth_prerelease.py +0 -0
  172. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_bug_036430.py +0 -0
  173. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_bug_036750.py +0 -0
  174. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  175. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_cli_access.py +0 -0
  176. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_config_cmd.py +0 -0
  177. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_content_language.py +0 -0
  178. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  179. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  180. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_full_e2e.py +0 -0
  181. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_global_meta.py +0 -0
  182. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_guide.py +0 -0
  183. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_i18n.py +0 -0
  184. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_instance_history_cli.py +0 -0
  185. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration.py +0 -0
  186. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_approval.py +0 -0
  187. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  188. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_calendar.py +0 -0
  189. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_destructive.py +0 -0
  190. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_misc.py +0 -0
  191. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_post.py +0 -0
  192. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_social.py +0 -0
  193. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_v3.py +0 -0
  194. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_workflow.py +0 -0
  195. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  196. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  197. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_multi_profile.py +0 -0
  198. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  199. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  200. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_org_id_cli.py +0 -0
  201. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_org_id_docs.py +0 -0
  202. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_parameter_conventions.py +0 -0
  203. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  204. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  205. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_record_field_alias.py +0 -0
  206. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  207. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_record_write_fixes.py +0 -0
  208. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_select_add_option.py +0 -0
  209. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  210. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  211. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_update.py +0 -0
  212. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_update_mode.py +0 -0
  213. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  214. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  215. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  216. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  217. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_v3_session.py +0 -0
  218. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  219. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  220. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  221. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  222. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  223. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  224. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  225. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  226. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/utils/__init__.py +0 -0
  227. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/utils/formatting.py +0 -0
  228. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/utils/options.py +0 -0
  229. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli/utils/parameter_mapping.py +0 -0
  230. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/dependency_links.txt +0 -0
  231. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/entry_points.txt +0 -0
  232. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/requires.txt +0 -0
  233. {hap_cli-0.8.25 → hap_cli-0.8.27}/hap_cli.egg-info/top_level.txt +0 -0
  234. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/__init__.py +0 -0
  235. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/__main__.py +0 -0
  236. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/_wftest.py +0 -0
  237. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/charts.py +0 -0
  238. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/cleanup.py +0 -0
  239. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/compiler.py +0 -0
  240. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/config.py +0 -0
  241. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/errors.py +0 -0
  242. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/executor.py +0 -0
  243. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/fields.py +0 -0
  244. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/hap.py +0 -0
  245. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/recording/__init__.py +0 -0
  246. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/recording/console.py +0 -0
  247. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/recording/jsonl.py +0 -0
  248. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/recording/mirror.py +0 -0
  249. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/recording/report.py +0 -0
  250. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/schema.py +0 -0
  251. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/seed/__init__.py +0 -0
  252. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/seed/cli.py +0 -0
  253. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/seed/executor.py +0 -0
  254. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/seed/template.py +0 -0
  255. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/selftest.py +0 -0
  256. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/smoke.py +0 -0
  257. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/step.py +0 -0
  258. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/steps.py +0 -0
  259. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/store.py +0 -0
  260. {hap_cli-0.8.25 → hap_cli-0.8.27}/live_tests/workflow_dsl.py +0 -0
  261. {hap_cli-0.8.25 → hap_cli-0.8.27}/setup.cfg +0 -0
  262. {hap_cli-0.8.25 → hap_cli-0.8.27}/setup.py +0 -0
  263. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/INDEX.json +0 -0
  264. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  265. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  266. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/create_optionset.yaml +0 -0
  267. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/create_role.yaml +0 -0
  268. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  269. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/delete_record.yaml +0 -0
  270. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/delete_role.yaml +0 -0
  271. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  272. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_info.yaml +0 -0
  273. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  274. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  275. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  276. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_details.yaml +0 -0
  277. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  278. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_list.yaml +0 -0
  279. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  280. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  281. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  282. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  283. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_regions.yaml +0 -0
  284. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_role_details.yaml +0 -0
  285. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/get_role_list.yaml +0 -0
  286. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  287. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  288. {hap_cli-0.8.25 → hap_cli-0.8.27}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  289. {hap_cli-0.8.25 → hap_cli-0.8.27}/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.25
3
+ Version: 0.8.27
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.25"
3
+ __version__ = "0.8.27"
@@ -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.")
@@ -6,6 +6,11 @@ import click
6
6
 
7
7
  from hap_cli.context import pass_context
8
8
  from hap_cli.core import instance as inst_mod
9
+ from hap_cli.core import record as rec_mod
10
+ from hap_cli.commands.record_cmd import (
11
+ _fields_json_to_raw as _rec_fields_json_to_raw,
12
+ _parse_fields as _rec_parse_fields,
13
+ )
9
14
  from hap_cli.utils.formatting import output_json, output_table, output_kv
10
15
  from hap_cli.i18n import t
11
16
 
@@ -165,15 +170,110 @@ def instance_sign(ctx, instance_id, sign_to, before, work_id, opinion):
165
170
  ctx.handle_error(e)
166
171
 
167
172
 
173
+ def _unwritten_fields(session, worksheet_id, row_id, controls):
174
+ """Names of controls whose non-empty write did not land on the record.
175
+
176
+ A fill-in node only accepts the fields on its own form; anything else is
177
+ dropped silently. Only non-empty writes are checked, so deliberately
178
+ clearing a field is never reported.
179
+ """
180
+ intended = [
181
+ c["controlId"] for c in controls
182
+ if str(c.get("value") or "").strip() not in ("", "[]")
183
+ ]
184
+ if not intended:
185
+ return []
186
+ row = rec_mod.get_record(session, worksheet_id, row_id) or {}
187
+ # get_record returns the cell values as a JSON *string* under rowData.
188
+ values = row.get("rowData") or {}
189
+ if isinstance(values, str):
190
+ try:
191
+ values = _json.loads(values)
192
+ except ValueError:
193
+ return [] # unreadable read-back: don't block on a guess
194
+ if not isinstance(values, dict):
195
+ return []
196
+ return [cid for cid in intended
197
+ if str(values.get(cid) or "").strip() in ("", "[]")]
198
+
199
+
168
200
  @instance.command("submit")
169
201
  @click.argument("instance_id")
170
202
  @click.option("--work-id", "-w", default="", help="Work item ID")
171
203
  @click.option("--opinion", "-o", default="", help="Submit comment")
204
+ @click.option("--field", "-f", multiple=True,
205
+ help="Field value to fill in before submitting: FIELD=VALUE "
206
+ "(repeatable). FIELD is a field id, alias or name.")
207
+ @click.option("--fields-json", default="",
208
+ help="Field values to fill in before submitting, as one JSON "
209
+ "array: [{\"id\": \"<field id or alias>\", \"value\": ...}, ...]. "
210
+ "Use either this or -f, not both. Value formats per field "
211
+ "type are in `hap guide record`.")
212
+ @click.option("--dry-run", is_flag=True,
213
+ help="Resolve the task's record and show what would be written, "
214
+ "without filling anything in or submitting.")
172
215
  @pass_context
173
- def instance_submit(ctx, instance_id, work_id, opinion):
174
- """Submit a fill-in task."""
216
+ def instance_submit(ctx, instance_id, work_id, opinion, field, fields_json, dry_run):
217
+ """Submit a fill-in task, optionally filling in its form first.
218
+
219
+ The fields of a fill-in task are writable only while you hold the task,
220
+ so pass them here rather than editing the record separately. Working
221
+ through todos end to end is covered in `hap guide approval`.
222
+ """
223
+ if field and fields_json:
224
+ raise click.UsageError("Pass exactly one of -f or --fields-json.")
175
225
  try:
176
226
  session = ctx.get_session()
227
+ raw = None
228
+ if field or fields_json:
229
+ raw = (_rec_fields_json_to_raw(fields_json) if fields_json
230
+ else _rec_parse_fields(field))
231
+ # Read-only resolution first: a fill-in task carries the record it
232
+ # writes to, which the caller should not have to look up.
233
+ target = None
234
+ if raw is not None:
235
+ target = inst_mod.get_work_item(session, instance_id, work_id=work_id)
236
+ if not (target or {}).get("worksheetId"):
237
+ raise click.ClickException(
238
+ "Could not resolve the record behind this task, so there "
239
+ "is nothing to fill in. Check the instance and work id.")
240
+ if dry_run:
241
+ ctx.output(
242
+ {"instanceId": instance_id, "workId": work_id,
243
+ "target": target, "fields": raw, "submitted": False},
244
+ lambda d: click.echo(
245
+ f"Would fill {len(raw or [])} field(s) on "
246
+ f"{(target or {}).get('worksheetId', '-')}/"
247
+ f"{(target or {}).get('rowId', '-')} and submit."),
248
+ )
249
+ return
250
+ if raw is not None:
251
+ ws_id, row_id = target["worksheetId"], target.get("rowId", "")
252
+ controls = rec_mod.encode_controls_by_id(
253
+ session, ws_id, raw, for_update=True)
254
+ # The record itself may be read-only outside the task; the
255
+ # instance/work pair is what grants the write.
256
+ rec_mod.update_record(
257
+ session, ws_id, row_id, controls,
258
+ instance_id=instance_id, work_id=work_id,
259
+ )
260
+ # A field the task's form does not contain is dropped by the
261
+ # server without any error, so the write must be read back
262
+ # before the task is submitted — otherwise the value is lost
263
+ # and the task is gone with it.
264
+ dropped = _unwritten_fields(session, ws_id, row_id, controls)
265
+ if dropped:
266
+ # Report the names the caller typed, not internal ids.
267
+ labels = dict(zip(
268
+ [c.get("controlId") for c in controls],
269
+ [str(r.get("controlId")) for r in raw],
270
+ )) if len(controls) == len(raw) else {}
271
+ shown = [labels.get(cid, cid) for cid in dropped]
272
+ raise click.ClickException(
273
+ "These values were not saved, so the task was NOT "
274
+ "submitted: " + ", ".join(shown) + ". A fill-in task "
275
+ "only accepts the fields on its own form; fill the rest "
276
+ "separately, or drop them and submit again.")
177
277
  data = inst_mod.submit(session, instance_id, work_id, opinion)
178
278
  ctx.output(data, lambda d: click.echo("Submitted."))
179
279
  except Exception as e:
@@ -162,8 +162,17 @@ def _fields_json_to_raw(fields_json):
162
162
  "--app-id", "-a", default="",
163
163
  help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
164
164
  )
165
+ @click.option("--instance-id", default="",
166
+ help="Workflow instance the edit belongs to. Pass together with "
167
+ "--work-id to fill in the form of a fill-in task, whose "
168
+ "fields are only writable while you hold that task. To fill "
169
+ "a task and submit it in one step use `approval submit -f`; "
170
+ "see `hap guide approval`.")
171
+ @click.option("--work-id", default="",
172
+ help="Workflow work-item the edit belongs to. Use with --instance-id.")
165
173
  @pass_context
166
- def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id):
174
+ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id,
175
+ instance_id, work_id):
167
176
  """Update a record.
168
177
 
169
178
  Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
@@ -193,6 +202,7 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, ap
193
202
  data = rec_mod.update_record(
194
203
  session, worksheet_id, row_id, controls,
195
204
  trigger_workflow=not no_workflow, app_id=app_id,
205
+ instance_id=instance_id, work_id=work_id,
196
206
  )
197
207
  ctx.output(data, lambda d: click.echo(f"Updated: {d}"))
198
208
  except Exception as e:
@@ -374,16 +384,28 @@ def record_discussions(ctx, worksheet_id, row_id, page_size, page):
374
384
  @click.option("--app-id", default="", help="Application ID (auto-detected from the worksheet if omitted)")
375
385
  @click.option("--view-id", default="", help="View ID the discussion originates from")
376
386
  @click.option("--reply-id", default="", help="ID of the discussion being replied to")
387
+ @click.option("--file", "-f", "files", multiple=True,
388
+ help="Attach a file to the comment (repeatable). Accepts a local "
389
+ "path or an http(s) URL.")
377
390
  @pass_context
378
- def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id):
391
+ def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id, files):
379
392
  """Add a discussion/comment to a record."""
380
393
  try:
381
394
  session = ctx.get_session()
395
+ attachments = None
396
+ if files:
397
+ from hap_cli.core import upload as up_mod
398
+ descriptors = up_mod.source_descriptors(list(files))
399
+ attachments = up_mod.upload_files(
400
+ session, descriptors,
401
+ worksheet_id=worksheet_id, app_id=app_id,
402
+ )
382
403
  data = rec_mod.add_discussion(
383
404
  session, worksheet_id, row_id, message,
384
405
  app_id=app_id, view_id=view_id, reply_id=reply_id,
406
+ attachments=attachments,
385
407
  )
386
- ctx.output(data, lambda d: click.echo(f"Discussion added."))
408
+ ctx.output(data, lambda d: click.echo("Discussion added."))
387
409
  except Exception as e:
388
410
  ctx.handle_error(e)
389
411
 
@@ -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(
@@ -965,6 +965,8 @@ def update_record(
965
965
  controls: list[dict[str, Any]],
966
966
  trigger_workflow: bool = True,
967
967
  app_id: str = "",
968
+ instance_id: str = "",
969
+ work_id: str = "",
968
970
  ) -> dict[str, Any]:
969
971
  """Update an existing record.
970
972
 
@@ -973,6 +975,14 @@ def update_record(
973
975
  AddWorksheetRow, UpdateWorksheetRow's schema has no ``appId``, and
974
976
  inventing one would diverge from the frontend.
975
977
 
978
+ ``instance_id`` / ``work_id`` identify a workflow step the caller is
979
+ acting inside. A fill-in node (填写节点) grants write permission on the
980
+ fields of its own form for the duration of that step; the record itself
981
+ may well be read-only otherwise, in which case a plain update answers
982
+ "权限不足". Passing the pair makes the write happen under the node's
983
+ permission, exactly as the web form does (UpdateWorksheetRow's
984
+ ``instanceId`` / ``workId`` params).
985
+
976
986
  Args:
977
987
  session: Active session
978
988
  worksheet_id: Worksheet ID
@@ -980,21 +990,24 @@ def update_record(
980
990
  controls: List of {controlId, value} dicts
981
991
  trigger_workflow: Whether to trigger workflows
982
992
  app_id: Optional application ID (metadata scope only)
993
+ instance_id: Workflow instance ID the write belongs to
994
+ work_id: Workflow work-item ID the write belongs to
983
995
 
984
996
  Returns:
985
997
  Update result
986
998
  """
987
999
  _reject_workflow_suppression(trigger_workflow)
1000
+ payload: dict[str, Any] = {
1001
+ "worksheetId": worksheet_id,
1002
+ "rowId": row_id,
1003
+ "newOldControl": controls,
1004
+ }
1005
+ if instance_id:
1006
+ payload["instanceId"] = instance_id
1007
+ if work_id:
1008
+ payload["workId"] = work_id
988
1009
  return _raise_on_failed_write(
989
- session.api_call(
990
- "Worksheet",
991
- "UpdateWorksheetRow",
992
- {
993
- "worksheetId": worksheet_id,
994
- "rowId": row_id,
995
- "newOldControl": controls,
996
- },
997
- ),
1010
+ session.api_call("Worksheet", "UpdateWorksheetRow", payload),
998
1011
  "update",
999
1012
  )
1000
1013
 
@@ -1089,6 +1102,7 @@ def add_discussion(
1089
1102
  app_id: str = "",
1090
1103
  view_id: str = "",
1091
1104
  reply_id: str = "",
1105
+ attachments: Optional[list[dict[str, Any]]] = None,
1092
1106
  ) -> dict[str, Any]:
1093
1107
  """Add a discussion/comment to a record (sourceType 8 / worksheet-row).
1094
1108
 
@@ -1117,6 +1131,12 @@ def add_discussion(
1117
1131
  }
1118
1132
  if reply_id:
1119
1133
  data["replyId"] = reply_id
1134
+ # ``attachments`` carries uploaded-file descriptors (from
1135
+ # hap_cli.core.upload). The frontend WorkSheetCommenter sends them as a
1136
+ # JSON-encoded string under ``attachments`` (see pd-openweb
1137
+ # components/comment/commenter.jsx), so match that shape.
1138
+ if attachments:
1139
+ data["attachments"] = json.dumps(attachments, ensure_ascii=False)
1120
1140
  result = session.api_call("Discussion", "AddDiscussion", data)
1121
1141
  _ensure_discussion_ok(result, "add discussion")
1122
1142
  return result
@@ -0,0 +1,118 @@
1
+ # Todos and approvals: find, fill, act
2
+
3
+ `hap approval` handles workflow todos: listing them, filling in the form of a
4
+ fill-in task, approving or rejecting, withdrawing and re-initiating. This topic
5
+ covers which command each kind of todo needs, plus the traps you will hit
6
+ without reading it.
7
+
8
+ > 🚨 Core risk of this topic: **the fields of a fill-in task cannot be written
9
+ > with a plain record update**. `hap worksheet record update` answers
10
+ > 权限不足 (insufficient permission), and that error points you in entirely the
11
+ > wrong direction (it reads as "your account lacks permission"). The right way
12
+ > is section 3.
13
+
14
+ ## 1. See what is waiting
15
+
16
+ ```bash
17
+ hap approval todo-count # counts per kind
18
+ hap approval todo --type 3 # only "waiting for me to fill in"
19
+ ```
20
+
21
+ `--type` values: `-1` all, `0` initiated by me, `3` fill in, `4` approve,
22
+ `5` carbon copy.
23
+
24
+ Each todo carries two IDs that every later command needs:
25
+
26
+ - `id` — the **workflow instance id** (instanceId). It is named `id`, which is
27
+ easy to mistake for an id of the todo itself.
28
+ - `workId` — the **work-item id**, identifying this step for this person.
29
+
30
+ To find which business record a todo belongs to:
31
+
32
+ ```bash
33
+ hap approval work-item <instanceId> --work-id <workId> # -> {worksheetId, rowId}
34
+ ```
35
+
36
+ ## 2. Approval tasks (type 4)
37
+
38
+ ```bash
39
+ hap approval approve <instanceId> --work-id <workId> -o "Agreed"
40
+ hap approval reject <instanceId> --work-id <workId> -o "Does not qualify"
41
+ hap approval operations <instanceId> --work-id <workId> # what this step actually allows
42
+ ```
43
+
44
+ When unsure which buttons a task supports, read `operations` first rather than
45
+ guessing.
46
+
47
+ ## 3. Fill-in tasks (type 3): fill the form and submit
48
+
49
+ **Do it in one command, not two:**
50
+
51
+ ```bash
52
+ hap approval submit <instanceId> --work-id <workId> \
53
+ -f 'Frontend owner=谭宏' -f 'Backend owner=陈旭' -o "Assigned"
54
+ ```
55
+
56
+ For richer values use `--fields-json` (same shape as writing a record; per-type
57
+ value formats are in `hap guide record`):
58
+
59
+ ```bash
60
+ hap approval submit <instanceId> --work-id <workId> \
61
+ --fields-json '[{"id":"Frontend owner","value":["<member accountId>"]}]'
62
+ ```
63
+
64
+ To see which record would be written without touching anything:
65
+
66
+ ```bash
67
+ hap approval submit <instanceId> --work-id <workId> -f 'Amount=100' --dry-run
68
+ ```
69
+
70
+ ### Why it has to go this way
71
+
72
+ A fill-in node's fields are writable **only while you hold the task, and only
73
+ when the write carries the workflow identity**. So:
74
+
75
+ - A plain `hap worksheet record update <ws> <row> -f 'field=value'` returns
76
+ **权限不足**. This is not an account-permission problem; do not go asking an
77
+ admin for rights.
78
+ - If you must edit the record separately, carry the workflow identity:
79
+ `hap worksheet record update <ws> <row> --instance-id <instanceId> --work-id <workId> ...`
80
+ That is exactly what `approval submit -f` does internally.
81
+
82
+ ### Only fields on the form can be filled
83
+
84
+ A fill-in node accepts **only the fields configured on its own form**. Anything
85
+ else is **discarded by the server with no error at all**. `approval submit`
86
+ therefore reads the write back before submitting; if a value did not land it
87
+ stops, names the field, and **does not submit** (the task survives, so you can
88
+ correct and retry). When you see that message, drop the field from the command
89
+ or have the flow designer add it to the node's form.
90
+
91
+ ## 4. Withdraw and re-initiate
92
+
93
+ ```bash
94
+ hap approval revoke <instanceId> # withdraw a flow you initiated
95
+ hap approval restart <instanceId> # re-initiate one you withdrew
96
+ ```
97
+
98
+ Notes:
99
+
100
+ - **Withdrawing uses the work item of the initiating step, not the approver's.**
101
+ Omit `--work-id` and let the CLI find that initiating work; it is more
102
+ reliable than filling one in by hand.
103
+ - `restart` requires the flow to have "allow re-initiation" enabled and
104
+ published, and the instance to actually be in the withdrawn state.
105
+
106
+ ## 5. Other common commands
107
+
108
+ ```bash
109
+ hap approval get <instanceId> # instance detail
110
+ hap approval op-history <instanceId> # processing history
111
+ hap approval urge <instanceId> --work-id <workId> # send a reminder
112
+ hap approval batch ... # bulk approve / reject
113
+ hap approval todo-filters ... # richer todo filtering
114
+ ```
115
+
116
+ The selection for a bulk operation is an **array of objects** (each with the
117
+ instance id and workId), not a list of id strings; see
118
+ `hap approval batch --help` for the exact parameters.
@@ -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,22 @@
24
24
  "zh-Hans": "顺着通知或讨论找到它所属的那条应用记录。"
25
25
  }
26
26
  },
27
+ {
28
+ "name": "approval",
29
+ "title": {"en": "Todos and approvals", "zh-Hans": "待办与审批"},
30
+ "summary": {
31
+ "en": "Find, fill in and act on workflow todos. A fill-in task's fields need the workflow identity, or the write is refused.",
32
+ "zh-Hans": "查看、填写、办理工作流待办。填写类待办的字段必须带工作流身份写,否则报权限不足。"
33
+ }
34
+ },
35
+ {
36
+ "name": "chart",
37
+ "title": {"en": "Charts and dashboards", "zh-Hans": "统计图与看板"},
38
+ "summary": {
39
+ "en": "Chart spec shape, editing a chart, placing it on a custom page. Miss a field and it saves fine but renders blank.",
40
+ "zh-Hans": "图表规格怎么写、怎么改图、怎么放进自定义页。写少了照样保存成功,页面却空白。"
41
+ }
42
+ },
27
43
  {
28
44
  "name": "skills",
29
45
  "title": {"en": "When to hand off", "zh-Hans": "什么时候转交专项 skill"},