acryl-datahub-cloud 0.3.10rc4__py3-none-any.whl → 0.3.16.1rc0__py3-none-any.whl

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.

Potentially problematic release.


This version of acryl-datahub-cloud might be problematic. Click here for more details.

Files changed (243) hide show
  1. acryl_datahub_cloud/_codegen_config.json +1 -1
  2. acryl_datahub_cloud/acryl_cs_issues/acryl_customer.py +1 -1
  3. acryl_datahub_cloud/acryl_cs_issues/models.py +5 -3
  4. acryl_datahub_cloud/action_request/action_request_owner_source.py +37 -8
  5. acryl_datahub_cloud/datahub_forms_notifications/__init__.py +0 -0
  6. acryl_datahub_cloud/datahub_forms_notifications/forms_notifications_source.py +569 -0
  7. acryl_datahub_cloud/datahub_forms_notifications/get_feature_flag.gql +7 -0
  8. acryl_datahub_cloud/datahub_forms_notifications/get_search_results_total.gql +14 -0
  9. acryl_datahub_cloud/datahub_forms_notifications/query.py +17 -0
  10. acryl_datahub_cloud/datahub_forms_notifications/scroll_forms_for_notification.gql +29 -0
  11. acryl_datahub_cloud/datahub_forms_notifications/send_form_notification_request.gql +5 -0
  12. acryl_datahub_cloud/datahub_reporting/datahub_dataset.py +39 -19
  13. acryl_datahub_cloud/datahub_reporting/datahub_form_reporting.py +60 -25
  14. acryl_datahub_cloud/datahub_reporting/extract_graph.py +9 -3
  15. acryl_datahub_cloud/datahub_reporting/extract_sql.py +248 -52
  16. acryl_datahub_cloud/datahub_reporting/forms.py +1 -1
  17. acryl_datahub_cloud/datahub_reporting/forms_config.py +3 -2
  18. acryl_datahub_cloud/datahub_restore/source.py +3 -2
  19. acryl_datahub_cloud/datahub_usage_reporting/excluded.py +94 -0
  20. acryl_datahub_cloud/datahub_usage_reporting/query_builder.py +48 -8
  21. acryl_datahub_cloud/datahub_usage_reporting/usage_feature_reporter.py +532 -109
  22. acryl_datahub_cloud/elasticsearch/graph_service.py +76 -14
  23. acryl_datahub_cloud/graphql_utils.py +64 -0
  24. acryl_datahub_cloud/lineage_features/source.py +555 -49
  25. acryl_datahub_cloud/metadata/_urns/urn_defs.py +2390 -1938
  26. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/actionworkflow/__init__.py +53 -0
  27. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/anomaly/__init__.py +2 -0
  28. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/application/__init__.py +19 -0
  29. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/assertion/__init__.py +6 -2
  30. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/common/__init__.py +6 -0
  31. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/conversation/__init__.py +29 -0
  32. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/event/notification/settings/__init__.py +2 -0
  33. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/execution/__init__.py +2 -0
  34. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/file/__init__.py +19 -0
  35. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/form/__init__.py +8 -0
  36. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/identity/__init__.py +8 -0
  37. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/knowledge/__init__.py +33 -0
  38. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/logical/__init__.py +15 -0
  39. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/metadata/key/__init__.py +14 -0
  40. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/metadata/search/features/__init__.py +2 -0
  41. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/module/__init__.py +31 -0
  42. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/monitor/__init__.py +6 -0
  43. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/notification/__init__.py +19 -0
  44. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/platform/event/v1/__init__.py +4 -0
  45. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/role/__init__.py +2 -0
  46. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/settings/asset/__init__.py +19 -0
  47. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/settings/global/__init__.py +28 -0
  48. acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/template/__init__.py +31 -0
  49. acryl_datahub_cloud/metadata/schema.avsc +27843 -23200
  50. acryl_datahub_cloud/metadata/schema_classes.py +29901 -24310
  51. acryl_datahub_cloud/metadata/schemas/ActionRequestInfo.avsc +235 -2
  52. acryl_datahub_cloud/metadata/schemas/ActionWorkflowInfo.avsc +683 -0
  53. acryl_datahub_cloud/metadata/schemas/ActionWorkflowKey.avsc +21 -0
  54. acryl_datahub_cloud/metadata/schemas/Actors.avsc +38 -1
  55. acryl_datahub_cloud/metadata/schemas/ApplicationKey.avsc +31 -0
  56. acryl_datahub_cloud/metadata/schemas/ApplicationProperties.avsc +75 -0
  57. acryl_datahub_cloud/metadata/schemas/Applications.avsc +38 -0
  58. acryl_datahub_cloud/metadata/schemas/AssertionAnalyticsRunEvent.avsc +375 -212
  59. acryl_datahub_cloud/metadata/schemas/AssertionInfo.avsc +147 -20
  60. acryl_datahub_cloud/metadata/schemas/AssertionKey.avsc +1 -1
  61. acryl_datahub_cloud/metadata/schemas/AssertionRunEvent.avsc +191 -21
  62. acryl_datahub_cloud/metadata/schemas/{AssertionSummary.avsc → AssertionRunSummary.avsc} +15 -2
  63. acryl_datahub_cloud/metadata/schemas/AssertionsSummary.avsc +54 -0
  64. acryl_datahub_cloud/metadata/schemas/AssetSettings.avsc +63 -0
  65. acryl_datahub_cloud/metadata/schemas/BusinessAttributeInfo.avsc +7 -3
  66. acryl_datahub_cloud/metadata/schemas/ChartInfo.avsc +20 -6
  67. acryl_datahub_cloud/metadata/schemas/ChartKey.avsc +1 -0
  68. acryl_datahub_cloud/metadata/schemas/ConstraintInfo.avsc +12 -1
  69. acryl_datahub_cloud/metadata/schemas/ContainerKey.avsc +1 -0
  70. acryl_datahub_cloud/metadata/schemas/ContainerProperties.avsc +16 -5
  71. acryl_datahub_cloud/metadata/schemas/CorpGroupEditableInfo.avsc +2 -1
  72. acryl_datahub_cloud/metadata/schemas/CorpGroupInfo.avsc +7 -3
  73. acryl_datahub_cloud/metadata/schemas/CorpGroupKey.avsc +2 -1
  74. acryl_datahub_cloud/metadata/schemas/CorpGroupSettings.avsc +127 -2
  75. acryl_datahub_cloud/metadata/schemas/CorpUserEditableInfo.avsc +1 -1
  76. acryl_datahub_cloud/metadata/schemas/CorpUserInfo.avsc +18 -2
  77. acryl_datahub_cloud/metadata/schemas/CorpUserInvitationStatus.avsc +106 -0
  78. acryl_datahub_cloud/metadata/schemas/CorpUserKey.avsc +4 -1
  79. acryl_datahub_cloud/metadata/schemas/CorpUserSettings.avsc +304 -2
  80. acryl_datahub_cloud/metadata/schemas/CorpUserUsageFeatures.avsc +86 -0
  81. acryl_datahub_cloud/metadata/schemas/DashboardInfo.avsc +11 -5
  82. acryl_datahub_cloud/metadata/schemas/DashboardKey.avsc +1 -0
  83. acryl_datahub_cloud/metadata/schemas/DataContractKey.avsc +2 -1
  84. acryl_datahub_cloud/metadata/schemas/DataFlowInfo.avsc +15 -5
  85. acryl_datahub_cloud/metadata/schemas/DataFlowKey.avsc +1 -0
  86. acryl_datahub_cloud/metadata/schemas/DataHubAiConversationInfo.avsc +256 -0
  87. acryl_datahub_cloud/metadata/schemas/DataHubAiConversationKey.avsc +22 -0
  88. acryl_datahub_cloud/metadata/schemas/DataHubFileInfo.avsc +234 -0
  89. acryl_datahub_cloud/metadata/schemas/DataHubFileKey.avsc +22 -0
  90. acryl_datahub_cloud/metadata/schemas/DataHubIngestionSourceKey.avsc +2 -1
  91. acryl_datahub_cloud/metadata/schemas/DataHubOpenAPISchemaKey.avsc +22 -0
  92. acryl_datahub_cloud/metadata/schemas/DataHubPageModuleKey.avsc +21 -0
  93. acryl_datahub_cloud/metadata/schemas/DataHubPageModuleProperties.avsc +308 -0
  94. acryl_datahub_cloud/metadata/schemas/DataHubPageTemplateKey.avsc +21 -0
  95. acryl_datahub_cloud/metadata/schemas/DataHubPageTemplateProperties.avsc +251 -0
  96. acryl_datahub_cloud/metadata/schemas/DataHubPolicyInfo.avsc +12 -1
  97. acryl_datahub_cloud/metadata/schemas/DataJobInfo.avsc +13 -4
  98. acryl_datahub_cloud/metadata/schemas/DataJobInputOutput.avsc +8 -0
  99. acryl_datahub_cloud/metadata/schemas/DataJobKey.avsc +1 -0
  100. acryl_datahub_cloud/metadata/schemas/DataPlatformInfo.avsc +3 -1
  101. acryl_datahub_cloud/metadata/schemas/DataPlatformInstanceProperties.avsc +5 -2
  102. acryl_datahub_cloud/metadata/schemas/DataProcessKey.avsc +4 -0
  103. acryl_datahub_cloud/metadata/schemas/DataProductKey.avsc +2 -0
  104. acryl_datahub_cloud/metadata/schemas/DataProductProperties.avsc +6 -3
  105. acryl_datahub_cloud/metadata/schemas/DataTransformLogic.avsc +4 -2
  106. acryl_datahub_cloud/metadata/schemas/DataTypeInfo.avsc +5 -0
  107. acryl_datahub_cloud/metadata/schemas/DatasetKey.avsc +10 -2
  108. acryl_datahub_cloud/metadata/schemas/DatasetProperties.avsc +12 -5
  109. acryl_datahub_cloud/metadata/schemas/DatasetUsageStatistics.avsc +8 -0
  110. acryl_datahub_cloud/metadata/schemas/DocumentInfo.avsc +407 -0
  111. acryl_datahub_cloud/metadata/schemas/DocumentKey.avsc +35 -0
  112. acryl_datahub_cloud/metadata/schemas/DocumentSettings.avsc +79 -0
  113. acryl_datahub_cloud/metadata/schemas/DomainKey.avsc +2 -0
  114. acryl_datahub_cloud/metadata/schemas/DomainProperties.avsc +7 -3
  115. acryl_datahub_cloud/metadata/schemas/EditableContainerProperties.avsc +2 -1
  116. acryl_datahub_cloud/metadata/schemas/EditableDashboardProperties.avsc +2 -1
  117. acryl_datahub_cloud/metadata/schemas/EditableDataFlowProperties.avsc +2 -1
  118. acryl_datahub_cloud/metadata/schemas/EditableDataJobProperties.avsc +2 -1
  119. acryl_datahub_cloud/metadata/schemas/EditableDatasetProperties.avsc +2 -1
  120. acryl_datahub_cloud/metadata/schemas/EditableERModelRelationshipProperties.avsc +2 -1
  121. acryl_datahub_cloud/metadata/schemas/EditableMLFeatureProperties.avsc +2 -1
  122. acryl_datahub_cloud/metadata/schemas/EditableMLFeatureTableProperties.avsc +2 -1
  123. acryl_datahub_cloud/metadata/schemas/EditableMLModelGroupProperties.avsc +2 -1
  124. acryl_datahub_cloud/metadata/schemas/EditableMLModelProperties.avsc +2 -1
  125. acryl_datahub_cloud/metadata/schemas/EditableNotebookProperties.avsc +2 -1
  126. acryl_datahub_cloud/metadata/schemas/EditableSchemaMetadata.avsc +4 -2
  127. acryl_datahub_cloud/metadata/schemas/EntityTypeInfo.avsc +5 -0
  128. acryl_datahub_cloud/metadata/schemas/ExecutionRequestArtifactsLocation.avsc +16 -0
  129. acryl_datahub_cloud/metadata/schemas/ExecutionRequestKey.avsc +2 -1
  130. acryl_datahub_cloud/metadata/schemas/FormAssignmentStatus.avsc +36 -0
  131. acryl_datahub_cloud/metadata/schemas/FormInfo.avsc +6 -0
  132. acryl_datahub_cloud/metadata/schemas/FormKey.avsc +3 -1
  133. acryl_datahub_cloud/metadata/schemas/FormNotifications.avsc +69 -0
  134. acryl_datahub_cloud/metadata/schemas/FormSettings.avsc +30 -0
  135. acryl_datahub_cloud/metadata/schemas/GlobalSettingsInfo.avsc +416 -0
  136. acryl_datahub_cloud/metadata/schemas/GlobalTags.avsc +2 -1
  137. acryl_datahub_cloud/metadata/schemas/GlossaryNodeInfo.avsc +3 -1
  138. acryl_datahub_cloud/metadata/schemas/GlossaryNodeKey.avsc +1 -0
  139. acryl_datahub_cloud/metadata/schemas/GlossaryTermInfo.avsc +3 -1
  140. acryl_datahub_cloud/metadata/schemas/GlossaryTermKey.avsc +2 -0
  141. acryl_datahub_cloud/metadata/schemas/IcebergWarehouseInfo.avsc +4 -0
  142. acryl_datahub_cloud/metadata/schemas/IncidentActivityEvent.avsc +3 -3
  143. acryl_datahub_cloud/metadata/schemas/IncidentInfo.avsc +3 -3
  144. acryl_datahub_cloud/metadata/schemas/InferredMetadata.avsc +71 -1
  145. acryl_datahub_cloud/metadata/schemas/InputFields.avsc +2 -1
  146. acryl_datahub_cloud/metadata/schemas/InviteToken.avsc +26 -0
  147. acryl_datahub_cloud/metadata/schemas/LineageFeatures.avsc +67 -42
  148. acryl_datahub_cloud/metadata/schemas/LogicalParent.avsc +145 -0
  149. acryl_datahub_cloud/metadata/schemas/MLFeatureKey.avsc +4 -1
  150. acryl_datahub_cloud/metadata/schemas/MLFeatureTableKey.avsc +4 -1
  151. acryl_datahub_cloud/metadata/schemas/MLModelDeploymentKey.avsc +7 -1
  152. acryl_datahub_cloud/metadata/schemas/MLModelDeploymentProperties.avsc +3 -0
  153. acryl_datahub_cloud/metadata/schemas/MLModelGroupKey.avsc +9 -1
  154. acryl_datahub_cloud/metadata/schemas/MLModelKey.avsc +9 -1
  155. acryl_datahub_cloud/metadata/schemas/MLModelProperties.avsc +4 -2
  156. acryl_datahub_cloud/metadata/schemas/MLPrimaryKeyKey.avsc +4 -1
  157. acryl_datahub_cloud/metadata/schemas/MetadataChangeEvent.avsc +424 -97
  158. acryl_datahub_cloud/metadata/schemas/MetadataChangeLog.avsc +65 -44
  159. acryl_datahub_cloud/metadata/schemas/MetadataChangeProposal.avsc +64 -0
  160. acryl_datahub_cloud/metadata/schemas/MonitorAnomalyEvent.avsc +84 -29
  161. acryl_datahub_cloud/metadata/schemas/MonitorInfo.avsc +221 -23
  162. acryl_datahub_cloud/metadata/schemas/MonitorKey.avsc +9 -1
  163. acryl_datahub_cloud/metadata/schemas/MonitorSuiteInfo.avsc +128 -3
  164. acryl_datahub_cloud/metadata/schemas/NotebookInfo.avsc +5 -2
  165. acryl_datahub_cloud/metadata/schemas/NotebookKey.avsc +1 -0
  166. acryl_datahub_cloud/metadata/schemas/NotificationRequest.avsc +91 -4
  167. acryl_datahub_cloud/metadata/schemas/Operation.avsc +17 -0
  168. acryl_datahub_cloud/metadata/schemas/Ownership.avsc +71 -1
  169. acryl_datahub_cloud/metadata/schemas/QueryProperties.avsc +4 -2
  170. acryl_datahub_cloud/metadata/schemas/QuerySubjects.avsc +2 -13
  171. acryl_datahub_cloud/metadata/schemas/RelationshipChangeEvent.avsc +215 -0
  172. acryl_datahub_cloud/metadata/schemas/RoleProperties.avsc +3 -1
  173. acryl_datahub_cloud/metadata/schemas/SchemaFieldInfo.avsc +3 -1
  174. acryl_datahub_cloud/metadata/schemas/SchemaFieldKey.avsc +3 -0
  175. acryl_datahub_cloud/metadata/schemas/SchemaMetadata.avsc +2 -1
  176. acryl_datahub_cloud/metadata/schemas/SemanticContent.avsc +123 -0
  177. acryl_datahub_cloud/metadata/schemas/StructuredProperties.avsc +69 -0
  178. acryl_datahub_cloud/metadata/schemas/StructuredPropertyDefinition.avsc +15 -4
  179. acryl_datahub_cloud/metadata/schemas/StructuredPropertySettings.avsc +9 -0
  180. acryl_datahub_cloud/metadata/schemas/SubscriptionInfo.avsc +136 -5
  181. acryl_datahub_cloud/metadata/schemas/SubscriptionKey.avsc +2 -1
  182. acryl_datahub_cloud/metadata/schemas/SystemMetadata.avsc +147 -0
  183. acryl_datahub_cloud/metadata/schemas/TagProperties.avsc +3 -1
  184. acryl_datahub_cloud/metadata/schemas/TestInfo.avsc +2 -1
  185. acryl_datahub_cloud/metadata/schemas/UpstreamLineage.avsc +9 -0
  186. acryl_datahub_cloud/metadata/schemas/UsageFeatures.avsc +10 -0
  187. acryl_datahub_cloud/metadata/schemas/__init__.py +3 -3
  188. acryl_datahub_cloud/notifications/__init__.py +0 -0
  189. acryl_datahub_cloud/notifications/notification_recipient_builder.py +399 -0
  190. acryl_datahub_cloud/sdk/__init__.py +69 -0
  191. acryl_datahub_cloud/sdk/assertion/__init__.py +58 -0
  192. acryl_datahub_cloud/sdk/assertion/assertion_base.py +779 -0
  193. acryl_datahub_cloud/sdk/assertion/column_metric_assertion.py +191 -0
  194. acryl_datahub_cloud/sdk/assertion/column_value_assertion.py +431 -0
  195. acryl_datahub_cloud/sdk/assertion/freshness_assertion.py +201 -0
  196. acryl_datahub_cloud/sdk/assertion/schema_assertion.py +268 -0
  197. acryl_datahub_cloud/sdk/assertion/smart_column_metric_assertion.py +212 -0
  198. acryl_datahub_cloud/sdk/assertion/smart_freshness_assertion.py +165 -0
  199. acryl_datahub_cloud/sdk/assertion/smart_sql_assertion.py +156 -0
  200. acryl_datahub_cloud/sdk/assertion/smart_volume_assertion.py +162 -0
  201. acryl_datahub_cloud/sdk/assertion/sql_assertion.py +273 -0
  202. acryl_datahub_cloud/sdk/assertion/types.py +20 -0
  203. acryl_datahub_cloud/sdk/assertion/volume_assertion.py +156 -0
  204. acryl_datahub_cloud/sdk/assertion_client/__init__.py +0 -0
  205. acryl_datahub_cloud/sdk/assertion_client/column_metric.py +545 -0
  206. acryl_datahub_cloud/sdk/assertion_client/column_value.py +617 -0
  207. acryl_datahub_cloud/sdk/assertion_client/freshness.py +371 -0
  208. acryl_datahub_cloud/sdk/assertion_client/helpers.py +166 -0
  209. acryl_datahub_cloud/sdk/assertion_client/schema.py +358 -0
  210. acryl_datahub_cloud/sdk/assertion_client/smart_column_metric.py +540 -0
  211. acryl_datahub_cloud/sdk/assertion_client/smart_freshness.py +373 -0
  212. acryl_datahub_cloud/sdk/assertion_client/smart_sql.py +411 -0
  213. acryl_datahub_cloud/sdk/assertion_client/smart_volume.py +380 -0
  214. acryl_datahub_cloud/sdk/assertion_client/sql.py +410 -0
  215. acryl_datahub_cloud/sdk/assertion_client/volume.py +446 -0
  216. acryl_datahub_cloud/sdk/assertion_input/__init__.py +0 -0
  217. acryl_datahub_cloud/sdk/assertion_input/assertion_input.py +1470 -0
  218. acryl_datahub_cloud/sdk/assertion_input/column_assertion_constants.py +114 -0
  219. acryl_datahub_cloud/sdk/assertion_input/column_assertion_utils.py +284 -0
  220. acryl_datahub_cloud/sdk/assertion_input/column_metric_assertion_input.py +759 -0
  221. acryl_datahub_cloud/sdk/assertion_input/column_metric_constants.py +109 -0
  222. acryl_datahub_cloud/sdk/assertion_input/column_value_assertion_input.py +810 -0
  223. acryl_datahub_cloud/sdk/assertion_input/freshness_assertion_input.py +305 -0
  224. acryl_datahub_cloud/sdk/assertion_input/schema_assertion_input.py +413 -0
  225. acryl_datahub_cloud/sdk/assertion_input/smart_column_metric_assertion_input.py +793 -0
  226. acryl_datahub_cloud/sdk/assertion_input/smart_freshness_assertion_input.py +218 -0
  227. acryl_datahub_cloud/sdk/assertion_input/smart_sql_assertion_input.py +181 -0
  228. acryl_datahub_cloud/sdk/assertion_input/smart_volume_assertion_input.py +189 -0
  229. acryl_datahub_cloud/sdk/assertion_input/sql_assertion_input.py +320 -0
  230. acryl_datahub_cloud/sdk/assertion_input/volume_assertion_input.py +635 -0
  231. acryl_datahub_cloud/sdk/assertions_client.py +1074 -0
  232. acryl_datahub_cloud/sdk/entities/__init__.py +0 -0
  233. acryl_datahub_cloud/sdk/entities/assertion.py +439 -0
  234. acryl_datahub_cloud/sdk/entities/monitor.py +291 -0
  235. acryl_datahub_cloud/sdk/entities/subscription.py +100 -0
  236. acryl_datahub_cloud/sdk/errors.py +34 -0
  237. acryl_datahub_cloud/sdk/resolver_client.py +42 -0
  238. acryl_datahub_cloud/sdk/subscription_client.py +737 -0
  239. {acryl_datahub_cloud-0.3.10rc4.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/METADATA +49 -43
  240. {acryl_datahub_cloud-0.3.10rc4.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/RECORD +243 -145
  241. {acryl_datahub_cloud-0.3.10rc4.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/WHEEL +1 -1
  242. {acryl_datahub_cloud-0.3.10rc4.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/entry_points.txt +1 -0
  243. {acryl_datahub_cloud-0.3.10rc4.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,569 @@
1
+ import logging
2
+ import time
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
5
+
6
+ from pydantic import BaseModel
7
+ from tenacity import (
8
+ retry,
9
+ retry_if_exception_type,
10
+ stop_after_attempt,
11
+ wait_exponential,
12
+ )
13
+
14
+ from acryl_datahub_cloud.datahub_forms_notifications.query import (
15
+ GRAPHQL_GET_FEATURE_FLAG,
16
+ GRAPHQL_GET_SEARCH_RESULTS_TOTAL,
17
+ GRAPHQL_SCROLL_FORMS_FOR_NOTIFICATIONS,
18
+ GRAPHQL_SEND_FORM_NOTIFICATION_REQUEST,
19
+ )
20
+ from acryl_datahub_cloud.graphql_utils import parse_extra_properties_for_model
21
+ from acryl_datahub_cloud.notifications.notification_recipient_builder import (
22
+ NotificationRecipientBuilder,
23
+ )
24
+ from datahub.emitter.mcp import MetadataChangeProposalWrapper
25
+ from datahub.ingestion.api.common import PipelineContext
26
+ from datahub.ingestion.api.decorators import (
27
+ SupportStatus,
28
+ config_class,
29
+ platform_name,
30
+ support_status,
31
+ )
32
+ from datahub.ingestion.api.source import Source, SourceReport
33
+ from datahub.ingestion.api.workunit import MetadataWorkUnit
34
+ from datahub.ingestion.graph.client import DataHubGraph
35
+ from datahub.ingestion.graph.filters import RawSearchFilter
36
+ from datahub.metadata.schema_classes import (
37
+ FormInfoClass,
38
+ FormNotificationDetailsClass,
39
+ FormNotificationEntryClass,
40
+ FormNotificationsClass,
41
+ FormSettingsClass,
42
+ FormStateClass,
43
+ FormTypeClass,
44
+ )
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+ USER_URN_PREFIX = "urn:li:corpuser"
49
+ GROUP_URN_PREFIX = "urn:li:corpGroup"
50
+
51
+
52
+ class DataHubFormsNotificationsSourceConfig(BaseModel):
53
+ form_urns: Optional[List[str]] = None
54
+
55
+
56
+ class DataHubDatasetSearchRow(BaseModel):
57
+ urn: str
58
+ owners: List[str] = []
59
+
60
+
61
+ @dataclass
62
+ class DataHubFormsNotificationsSourceReport(SourceReport):
63
+ notifications_sent: int = (
64
+ 0 # the number of recipients we sent notifications out for
65
+ )
66
+ forms_count: int = (
67
+ 0 # the number of forms that we sent at least one nitification for
68
+ )
69
+
70
+
71
+ @platform_name(id="datahub", platform_name="DataHub")
72
+ @config_class(DataHubFormsNotificationsSourceConfig)
73
+ @support_status(SupportStatus.INCUBATING)
74
+ class DataHubFormsNotificationsSource(Source):
75
+ """Forms Notification Source that notifies recipients for compliance forms tasks"""
76
+
77
+ def __init__(
78
+ self, config: DataHubFormsNotificationsSourceConfig, ctx: PipelineContext
79
+ ):
80
+ super().__init__(ctx)
81
+ self.config: DataHubFormsNotificationsSourceConfig = config
82
+ self.report = DataHubFormsNotificationsSourceReport()
83
+ self.graph: DataHubGraph = ctx.require_graph(
84
+ "Loading default graph coordinates."
85
+ )
86
+ self.group_to_users_map: Dict[str, List[str]] = {}
87
+ self.recipient_builder: NotificationRecipientBuilder = (
88
+ NotificationRecipientBuilder(self.graph)
89
+ )
90
+ self.user_to_form_notifications: Dict[str, FormNotificationsClass] = {}
91
+
92
+ def get_workunits(self) -> Iterable[MetadataWorkUnit]:
93
+ # end early if the feature flag is not enabled
94
+ if not self.is_feature_flag_enabled():
95
+ return []
96
+
97
+ self.notify_form_assignees()
98
+
99
+ # This source doesn't produce any work units
100
+ return []
101
+
102
+ def is_feature_flag_enabled(self) -> bool:
103
+ response = self.execute_graphql_with_retry(
104
+ GRAPHQL_GET_FEATURE_FLAG, variables={}
105
+ )
106
+
107
+ result = response.get("appConfig", {})
108
+ featureFlags = result.get("featureFlags", {})
109
+ is_enabled = featureFlags.get("formsNotificationsEnabled", False)
110
+
111
+ if not is_enabled:
112
+ logger.error(
113
+ "Tried running datahub-forms-notifications with formsNotificationsEnabled disabled"
114
+ )
115
+
116
+ return is_enabled
117
+
118
+ def notify_form_assignees(self) -> None:
119
+ for urn, form in self.get_forms():
120
+ if not self.is_form_complete(urn, form.type):
121
+ assignees = self.get_form_assignees(urn, form)
122
+ self.process_notify_on_publish(
123
+ assignees, form.name, urn, form.description
124
+ )
125
+
126
+ def process_notify_on_publish(
127
+ self,
128
+ form_assignees: List[str],
129
+ form_name: str,
130
+ form_urn: str,
131
+ form_details: str | None,
132
+ ) -> None:
133
+ """
134
+ Take in form assignees, find the ones who haven't been notified on publish, and build a notification for them.
135
+ """
136
+ filtered_assignees = self.filter_assignees_to_notify(form_assignees, form_urn)
137
+ recipients = []
138
+ if self.recipient_builder is not None:
139
+ recipients = self.recipient_builder.build_actor_recipients(
140
+ filtered_assignees, "COMPLIANCE_FORM_PUBLISH", True
141
+ )
142
+ recipient_count = len(recipients)
143
+
144
+ if recipient_count > 0:
145
+ self.report.notifications_sent += recipient_count
146
+ self.report.forms_count += 1
147
+
148
+ parameters = [{"key": "formName", "value": form_name}]
149
+ if form_details is not None:
150
+ parameters.append({"key": "formDetails", "value": form_details})
151
+
152
+ response = self.execute_graphql_with_retry(
153
+ GRAPHQL_SEND_FORM_NOTIFICATION_REQUEST,
154
+ variables={
155
+ "input": {
156
+ "type": "BROADCAST_COMPLIANCE_FORM_PUBLISH",
157
+ "parameters": parameters,
158
+ "recipients": self.recipient_builder.convert_recipients_to_json_objects(
159
+ recipients
160
+ ),
161
+ }
162
+ },
163
+ )
164
+
165
+ if not response.get("sendFormNotificationRequest", False):
166
+ logger.error(
167
+ f"Issue sending the notification request for this job. Response: {response}"
168
+ )
169
+ else:
170
+ unique_actor_urns = set(
171
+ [
172
+ recipient.get("actor")
173
+ for recipient in recipients
174
+ if recipient.get("actor") is not None
175
+ ]
176
+ )
177
+ for actor_urn in unique_actor_urns:
178
+ self.update_form_notifications(actor_urn, form_urn)
179
+
180
+ def update_form_notifications(self, user_urn: str, form_urn: str) -> None:
181
+ """
182
+ After sending a notification, update the user's formNotifications aspect
183
+ to track that we sent the notification that we did
184
+ """
185
+ # get or create default formNotifications aspect
186
+ form_notifications = self.user_to_form_notifications.get(user_urn)
187
+ if form_notifications is None:
188
+ form_notifications = FormNotificationsClass(notificationDetails=[])
189
+
190
+ # get the notification details for our specific form or create default
191
+ details_for_form = self.get_notification_details_for_form(
192
+ user_urn, form_urn, form_notifications
193
+ )
194
+ if details_for_form is None:
195
+ details_for_form = FormNotificationDetailsClass(
196
+ formUrn=form_urn, notificationLog=[]
197
+ )
198
+
199
+ # add new notification log entry for this occasion
200
+ new_notification_log_entry = FormNotificationEntryClass(
201
+ time=int(time.time() * 1000),
202
+ notificationType="BROADCAST_COMPLIANCE_FORM_PUBLISH",
203
+ )
204
+ details_for_form.notificationLog.append(new_notification_log_entry)
205
+
206
+ # filter out details for given form so we can add updated one
207
+ final_notification_details = [
208
+ details
209
+ for details in form_notifications.notificationDetails
210
+ if details.formUrn != form_urn
211
+ ]
212
+ final_notification_details.append(details_for_form)
213
+
214
+ # update the aspect with the final notification details
215
+ form_notifications.notificationDetails = final_notification_details
216
+
217
+ self.user_to_form_notifications[user_urn] = form_notifications
218
+
219
+ self.graph.emit(
220
+ MetadataChangeProposalWrapper(
221
+ entityUrn=user_urn,
222
+ aspect=form_notifications,
223
+ )
224
+ )
225
+
226
+ @retry(
227
+ retry=retry_if_exception_type((Exception, ConnectionError)),
228
+ stop=stop_after_attempt(3),
229
+ wait=wait_exponential(multiplier=1, min=4, max=10),
230
+ reraise=True,
231
+ )
232
+ def execute_graphql_with_retry(
233
+ self, query: str, variables: Dict[str, Any]
234
+ ) -> Dict[str, Any]:
235
+ """Execute GraphQL query with retry logic"""
236
+ if self.graph is None:
237
+ raise ValueError("Graph client not initialized")
238
+ response = self.graph.execute_graphql(query, variables=variables)
239
+ error = response.get("error")
240
+ if error:
241
+ raise Exception(f"GraphQL error: {error}")
242
+ return response
243
+
244
+ def get_forms(self) -> List[Tuple[str, FormInfoClass]]:
245
+ """
246
+ Get forms and their formInfo aspect either from the forms provided in the config
247
+ or search for forms that are published and notifyAssigneesOnPublish = True.
248
+ This method will only return forms that are published and have notifications enabled.
249
+ """
250
+ form_urns = []
251
+
252
+ if self.config.form_urns is not None:
253
+ form_urns = self.config.form_urns
254
+ else:
255
+ form_urns = self.search_for_forms()
256
+
257
+ form_urns_with_notifications_enabled = (
258
+ self.get_form_urns_with_notifications_enabled(form_urns)
259
+ )
260
+
261
+ return self.get_form_infos(form_urns_with_notifications_enabled)
262
+
263
+ def get_form_urns_with_notifications_enabled(
264
+ self, form_urns: List[str]
265
+ ) -> List[str]:
266
+ """
267
+ Get formSettings aspects and check if notifications are enabled for a given form urn.
268
+ If notifications are enabled, add to filtered list and return.
269
+ """
270
+ filtered_form_urns: List[str] = []
271
+
272
+ if len(form_urns) > 0:
273
+ entities = self.graph.get_entities("form", form_urns, ["formSettings"])
274
+ for urn, entity in entities.items():
275
+ form_tuple = entity.get(FormSettingsClass.ASPECT_NAME, (None, None))
276
+ if form_tuple and form_tuple[0]:
277
+ if not isinstance(form_tuple[0], FormSettingsClass):
278
+ logger.error(
279
+ f"{form_tuple[0]} is not of type FormInfo for urn: {urn}"
280
+ )
281
+ else:
282
+ form_settings = form_tuple[0]
283
+ if form_settings.notificationSettings.notifyAssigneesOnPublish:
284
+ filtered_form_urns.append(urn)
285
+
286
+ return filtered_form_urns
287
+
288
+ def get_form_infos(self, form_urns: List[str]) -> List[Tuple[str, FormInfoClass]]:
289
+ """
290
+ Get formInfo aspects for a list of form urns and return the formInfos of forms
291
+ that are published. If a form is not published, we don't want to notify.
292
+ """
293
+ form_infos: List[Tuple[str, FormInfoClass]] = []
294
+
295
+ if len(form_urns) > 0:
296
+ entities = self.graph.get_entities("form", form_urns, ["formInfo"])
297
+ for urn, entity in entities.items():
298
+ form_tuple = entity.get(FormInfoClass.ASPECT_NAME, (None, None))
299
+ if form_tuple and form_tuple[0]:
300
+ if not isinstance(form_tuple[0], FormInfoClass):
301
+ logger.error(
302
+ f"{form_tuple[0]} is not of type FormInfo for urn: {urn}"
303
+ )
304
+ else:
305
+ form_info = form_tuple[0]
306
+ if form_info.status.state == FormStateClass.PUBLISHED:
307
+ form_infos.append((urn, form_tuple[0]))
308
+
309
+ return form_infos
310
+
311
+ def search_for_forms(self) -> List[str]:
312
+ scroll_id: Optional[str] = None
313
+ form_urns: List[str] = []
314
+
315
+ try:
316
+ while True:
317
+ next_scroll_id, results = self.scroll_forms_to_notify_for(scroll_id)
318
+
319
+ for result in results:
320
+ form_urn = result.get("entity", {}).get("urn", None)
321
+ if form_urn is None:
322
+ self.report.report_warning(
323
+ message="Failed to resolve entity urn for form! Skipping...",
324
+ context=f"Response: {str(result)}",
325
+ )
326
+ else:
327
+ form_urns.append(form_urn)
328
+
329
+ if next_scroll_id is None:
330
+ break
331
+ else:
332
+ scroll_id = next_scroll_id
333
+
334
+ time.sleep(1)
335
+
336
+ except Exception as e:
337
+ self.report.report_failure(
338
+ title="Failed to search for forms to send notifications for",
339
+ message="Error occurred while searching for forms to send notifications for",
340
+ context=f"message = {str(e)}",
341
+ exc=e,
342
+ )
343
+ return form_urns
344
+
345
+ return form_urns
346
+
347
+ def scroll_forms_to_notify_for(
348
+ self, scroll_id: Optional[str]
349
+ ) -> Tuple[Optional[str], List[Dict[str, Any]]]:
350
+ """Scroll through shared entities with retry logic"""
351
+ response = self.execute_graphql_with_retry(
352
+ GRAPHQL_SCROLL_FORMS_FOR_NOTIFICATIONS,
353
+ variables={
354
+ "scrollId": scroll_id,
355
+ "count": 500,
356
+ },
357
+ )
358
+
359
+ result = response.get("scrollAcrossEntities", {})
360
+ return result.get("nextScrollId"), result.get("searchResults", [])
361
+
362
+ def get_form_assignees(self, form_urn: str, form: FormInfoClass) -> List[str]:
363
+ """
364
+ Form assignees are provided explicitly on the form and the owners of assets with this form
365
+ if it's an ownership form.
366
+ For form notifications, we want to get users from a user group and send notifications to
367
+ those users specifically
368
+ """
369
+ user_urns = form.actors.users if form.actors.users is not None else []
370
+ group_urns = form.actors.groups if form.actors.groups is not None else []
371
+
372
+ if form.actors.owners:
373
+ (user_owners, group_owners) = self.get_owners_of_assets_for_form(
374
+ form_urn, form
375
+ )
376
+ user_urns.extend(user_owners)
377
+ group_urns.extend(group_owners)
378
+
379
+ for group_urn in group_urns:
380
+ user_urns.extend(self._get_users_in_group(group_urn))
381
+
382
+ return list(set(user_urns))
383
+
384
+ def get_owners_of_assets_for_form(
385
+ self, form_urn: str, form: FormInfoClass
386
+ ) -> Tuple[List[str], List[str]]:
387
+ """
388
+ Filter to get assets that are not complete for this form and using the extra_source_fields parameter
389
+ we pull owners from the asset's elastic row. self.graph.get_results_by_filter will paginate over assets
390
+ """
391
+ user_urns = []
392
+ group_urns = []
393
+
394
+ extra_fields = [f for f in DataHubDatasetSearchRow.model_fields]
395
+ results = self.graph.get_results_by_filter(
396
+ extra_or_filters=self._get_incomplete_assets_for_form(form_urn, form.type),
397
+ extra_source_fields=extra_fields,
398
+ skip_cache=True,
399
+ )
400
+ for result in results:
401
+ extra_properties = result["extraProperties"]
402
+ extra_properties_map = parse_extra_properties_for_model(
403
+ extra_properties, DataHubDatasetSearchRow
404
+ )
405
+ search_row = DataHubDatasetSearchRow(**extra_properties_map)
406
+ for owner in search_row.owners:
407
+ if owner.startswith(USER_URN_PREFIX):
408
+ user_urns.append(owner)
409
+ elif owner.startswith(GROUP_URN_PREFIX):
410
+ group_urns.append(owner)
411
+ else:
412
+ logger.warning(
413
+ f"Found unexpected owner {owner} for asset {search_row.urn}"
414
+ )
415
+
416
+ return (user_urns, group_urns)
417
+
418
+ def filter_assignees_to_notify(
419
+ self, user_urns: List[str], form_urn: str
420
+ ) -> List[str]:
421
+ """
422
+ Filter out any users who have already received the publish notification type in the past
423
+ """
424
+ filtered_users = []
425
+
426
+ self.populate_user_to_form_notifications(user_urns)
427
+
428
+ for user in user_urns:
429
+ form_notifications = self.user_to_form_notifications.get(user)
430
+ if form_notifications is None or not self.has_user_been_sent_notification(
431
+ user, form_urn, form_notifications, "BROADCAST_COMPLIANCE_FORM_PUBLISH"
432
+ ):
433
+ filtered_users.append(user)
434
+
435
+ return filtered_users
436
+
437
+ def has_user_been_sent_notification(
438
+ self,
439
+ user_urn: str,
440
+ form_urn: str,
441
+ form_notifications: FormNotificationsClass,
442
+ notification_type: str,
443
+ ) -> bool:
444
+ notification_details = self.get_notification_details_for_form(
445
+ user_urn, form_urn, form_notifications
446
+ )
447
+ if notification_details is None:
448
+ return False
449
+
450
+ notification_types_sent_for_form = [
451
+ entry.notificationType for entry in notification_details.notificationLog
452
+ ]
453
+
454
+ return notification_type in notification_types_sent_for_form
455
+
456
+ def get_notification_details_for_form(
457
+ self, user_urn: str, form_urn: str, form_notifications: FormNotificationsClass
458
+ ) -> FormNotificationDetailsClass | None:
459
+ notification_details_for_form = [
460
+ detail
461
+ for detail in form_notifications.notificationDetails
462
+ if detail.formUrn == form_urn
463
+ ]
464
+
465
+ notification_details = None
466
+ if len(notification_details_for_form) > 1:
467
+ logger.warning(
468
+ f"Found more than one notificationDetails for a given form for user {user_urn} in {form_notifications}"
469
+ )
470
+ # grab first one
471
+ notification_details = notification_details_for_form[0]
472
+ elif len(notification_details_for_form) == 1:
473
+ notification_details = notification_details_for_form[0]
474
+
475
+ return notification_details
476
+
477
+ def populate_user_to_form_notifications(self, user_urns: List[str]) -> None:
478
+ new_users = [
479
+ urn for urn in user_urns if urn not in self.user_to_form_notifications
480
+ ]
481
+
482
+ if len(new_users) == 0:
483
+ return
484
+
485
+ entities = self.graph.get_entities("corpuser", new_users, ["formNotifications"])
486
+ for urn, entity in entities.items():
487
+ user_tuple = entity.get(FormNotificationsClass.ASPECT_NAME, (None, None))
488
+ if user_tuple and user_tuple[0]:
489
+ if not isinstance(user_tuple[0], FormNotificationsClass):
490
+ logger.error(
491
+ f"{user_tuple[0]} is not of type FormNotifications for urn: {urn}"
492
+ )
493
+ else:
494
+ self.user_to_form_notifications[urn] = user_tuple[0]
495
+
496
+ def _get_users_in_group(self, group_urn: str) -> List[str]:
497
+ """
498
+ Using a relationship query, get users inside of a group. Store these users in memory if we've
499
+ already fetched the users for this group.
500
+ """
501
+ if (users_in_group := self.group_to_users_map.get(group_urn)) is not None:
502
+ return users_in_group
503
+
504
+ group_member_urns = []
505
+ members = self.graph.get_related_entities(
506
+ group_urn,
507
+ ["IsMemberOfGroup", "IsMemberOfNativeGroup"],
508
+ self.graph.RelationshipDirection.INCOMING,
509
+ )
510
+ member_urns = [member.urn for member in members]
511
+ for member_urn in member_urns:
512
+ if member_urn.startswith(USER_URN_PREFIX):
513
+ group_member_urns.append(member_urn)
514
+ else:
515
+ logger.warning(
516
+ f"Unexpected group member {member_urn} found in group {group_urn}"
517
+ )
518
+ self.group_to_users_map[group_urn] = group_member_urns
519
+
520
+ return group_member_urns
521
+
522
+ def _get_verification_form_filter(self, form_urn: str) -> RawSearchFilter:
523
+ return [
524
+ {"and": [{"field": "incompleteForms", "values": [form_urn]}]},
525
+ {
526
+ "and": [
527
+ {"field": "completedForms", "values": [form_urn]},
528
+ {"field": "verifiedForms", "values": [form_urn], "negated": True},
529
+ ]
530
+ },
531
+ ]
532
+
533
+ def _get_completion_form_filter(self, form_urn: str) -> RawSearchFilter:
534
+ return [{"and": [{"field": "incompleteForms", "values": [form_urn]}]}]
535
+
536
+ def _get_incomplete_assets_for_form(
537
+ self, form_urn: str, form_type: str | FormTypeClass
538
+ ) -> RawSearchFilter:
539
+ return (
540
+ self._get_completion_form_filter(form_urn)
541
+ if form_type == FormTypeClass.COMPLETION
542
+ else self._get_verification_form_filter(form_urn)
543
+ )
544
+
545
+ def is_form_complete(self, form_urn: str, form_type: str | FormTypeClass) -> int:
546
+ """
547
+ Returns whether this form is complete - meaning no assets have any work left to do for it.
548
+ This takes into account the type of form to know if it's fully complete.
549
+ """
550
+ response = self.execute_graphql_with_retry(
551
+ GRAPHQL_GET_SEARCH_RESULTS_TOTAL,
552
+ variables={
553
+ "count": 0,
554
+ "orFilters": self._get_incomplete_assets_for_form(form_urn, form_type),
555
+ },
556
+ )
557
+
558
+ result = response.get("searchAcrossEntities", {})
559
+ total = result.get("total", -1)
560
+ if total < 0:
561
+ logger.warning(
562
+ f"Error evaluating if form with urn {form_urn} is complete. Skipping."
563
+ )
564
+ return True
565
+ else:
566
+ return total == 0
567
+
568
+ def get_report(self) -> SourceReport:
569
+ return self.report
@@ -0,0 +1,7 @@
1
+ query getAppConfig {
2
+ appConfig {
3
+ featureFlags {
4
+ formsNotificationsEnabled
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,14 @@
1
+ query getSearchResultsTotal($orFilters: [AndFilterInput!]!, $count: Int!) {
2
+ searchAcrossEntities(
3
+ input: {
4
+ types: []
5
+ query: "*"
6
+ count: $count
7
+ searchFlags: { skipCache: true }
8
+ orFilters: $orFilters
9
+ }
10
+ ) {
11
+ count
12
+ total
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ import pathlib
2
+
3
+ GRAPHQL_SCROLL_FORMS_FOR_NOTIFICATIONS = (
4
+ pathlib.Path(__file__).parent / "scroll_forms_for_notification.gql"
5
+ ).read_text()
6
+
7
+ GRAPHQL_GET_SEARCH_RESULTS_TOTAL = (
8
+ pathlib.Path(__file__).parent / "get_search_results_total.gql"
9
+ ).read_text()
10
+
11
+ GRAPHQL_SEND_FORM_NOTIFICATION_REQUEST = (
12
+ pathlib.Path(__file__).parent / "send_form_notification_request.gql"
13
+ ).read_text()
14
+
15
+ GRAPHQL_GET_FEATURE_FLAG = (
16
+ pathlib.Path(__file__).parent / "get_feature_flag.gql"
17
+ ).read_text()
@@ -0,0 +1,29 @@
1
+ query scrollFormsToNotifyFor($scrollId: String, $count: Int!) {
2
+ scrollAcrossEntities(
3
+ input: {
4
+ types: [FORM]
5
+ query: "*"
6
+ scrollId: $scrollId
7
+ count: $count
8
+ searchFlags: { skipCache: true }
9
+ orFilters: [
10
+ {
11
+ and: [
12
+ { field: "formStatus", values: ["PUBLISHED"] }
13
+ { field: "notifyAssigneesOnPublish", values: ["true"] }
14
+ ]
15
+ }
16
+ ]
17
+ }
18
+ ) {
19
+ nextScrollId
20
+ count
21
+ total
22
+ searchResults {
23
+ entity {
24
+ urn
25
+ type
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ mutation sendFormNotificationRequest(
2
+ $input: SendFormNotificationRequestInput!
3
+ ) {
4
+ sendFormNotificationRequest(input: $input)
5
+ }