recce-nightly 0.62.0.20250417__py3-none-any.whl → 1.30.0.20251221__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 recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +845 -461
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +59 -42
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +41 -35
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +64 -25
- recce/artifact.py +119 -51
- recce/cli.py +1301 -324
- recce/config.py +43 -34
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -1
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +68 -0
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -7
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/diff.py +6 -12
- recce/event/CONFIG.bak +1 -0
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +725 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +438 -21
- recce/models/run.py +1 -0
- recce/models/types.py +134 -28
- recce/pull_request.py +27 -25
- recce/run.py +179 -122
- recce/server.py +394 -104
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +196 -149
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +180 -89
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +247 -155
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +105 -100
- recce/util/cll.py +274 -219
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +347 -72
- recce/util/singleton.py +4 -4
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +7 -10
- recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
- recce/state.py +0 -753
- recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
- recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
- recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -13
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
- tests/adapter/dbt_adapter/test_selector.py +0 -177
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -137
- tests/tasks/test_lineage.py +0 -42
- tests/tasks/test_preset_checks.py +0 -50
- tests/tasks/test_profile.py +0 -73
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -116
- tests/tasks/test_schema.py +0 -99
- tests/tasks/test_top_k.py +0 -73
- tests/tasks/test_valuediff.py +0 -74
- tests/test_cli.py +0 -122
- tests/test_config.py +0 -45
- tests/test_core.py +0 -27
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -98
- tests/test_state.py +0 -123
- tests/test_summary.py +0 -57
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recce Cloud API client for check event operations.
|
|
3
|
+
|
|
4
|
+
This module provides methods for managing check events (timeline/conversation) in Recce Cloud,
|
|
5
|
+
including CRUD operations for comments and retrieving state change events.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
from recce.util.cloud.base import CloudBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CheckEventsCloud(CloudBase):
|
|
14
|
+
"""
|
|
15
|
+
Client for Recce Cloud check event operations.
|
|
16
|
+
|
|
17
|
+
Provides methods to list, create, update, and delete check events
|
|
18
|
+
(comments and state changes) within Recce Cloud sessions.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> client = CheckEventsCloud(token="your-api-token")
|
|
22
|
+
>>> events = client.list_events(
|
|
23
|
+
... org_id="org123",
|
|
24
|
+
... project_id="proj456",
|
|
25
|
+
... session_id="sess789",
|
|
26
|
+
... check_id="check001"
|
|
27
|
+
... )
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def _build_events_url(self, org_id: str, project_id: str, session_id: str, check_id: str) -> str:
|
|
31
|
+
"""Build the base URL for check events endpoints."""
|
|
32
|
+
return f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}/events"
|
|
33
|
+
|
|
34
|
+
def list_events(self, org_id: str, project_id: str, session_id: str, check_id: str) -> List[Dict]:
|
|
35
|
+
"""
|
|
36
|
+
List all events for a check in chronological order.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
org_id: Organization ID
|
|
40
|
+
project_id: Project ID
|
|
41
|
+
session_id: Session ID
|
|
42
|
+
check_id: Check ID
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of event dictionaries
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
RecceCloudException: If the request fails
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> events = client.list_events("org123", "proj456", "sess789", "check001")
|
|
52
|
+
>>> for event in events:
|
|
53
|
+
... print(f"{event['event_type']}: {event['content']}")
|
|
54
|
+
"""
|
|
55
|
+
api_url = self._build_events_url(org_id, project_id, session_id, check_id)
|
|
56
|
+
query_params = {"include_deleted": True}
|
|
57
|
+
response = self._request("GET", api_url, params=query_params)
|
|
58
|
+
|
|
59
|
+
self._raise_for_status(
|
|
60
|
+
response,
|
|
61
|
+
"Failed to list check events from Recce Cloud.",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
data = response.json()
|
|
65
|
+
# Response is wrapped: {"events": [...]}
|
|
66
|
+
return data.get("events", [])
|
|
67
|
+
|
|
68
|
+
def get_event(self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str) -> Dict:
|
|
69
|
+
"""
|
|
70
|
+
Get a specific event by ID.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
org_id: Organization ID
|
|
74
|
+
project_id: Project ID
|
|
75
|
+
session_id: Session ID
|
|
76
|
+
check_id: Check ID
|
|
77
|
+
event_id: Event ID
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Event dictionary
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
RecceCloudException: If the request fails or event not found
|
|
84
|
+
"""
|
|
85
|
+
api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
|
|
86
|
+
response = self._request("GET", api_url)
|
|
87
|
+
|
|
88
|
+
self._raise_for_status(
|
|
89
|
+
response,
|
|
90
|
+
f"Failed to get check event {event_id} from Recce Cloud.",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
data = response.json()
|
|
94
|
+
# Response is wrapped: {"event": {...}}
|
|
95
|
+
return data.get("event", {})
|
|
96
|
+
|
|
97
|
+
def create_comment(self, org_id: str, project_id: str, session_id: str, check_id: str, content: str) -> Dict:
|
|
98
|
+
"""
|
|
99
|
+
Create a new comment on a check.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
org_id: Organization ID
|
|
103
|
+
project_id: Project ID
|
|
104
|
+
session_id: Session ID
|
|
105
|
+
check_id: Check ID
|
|
106
|
+
content: Comment content (plain text or markdown)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Created event dictionary
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
RecceCloudException: If the request fails
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> event = client.create_comment(
|
|
116
|
+
... "org123", "proj456", "sess789", "check001",
|
|
117
|
+
... "This looks good to me!"
|
|
118
|
+
... )
|
|
119
|
+
>>> print(f"Created comment with ID: {event['id']}")
|
|
120
|
+
"""
|
|
121
|
+
api_url = self._build_events_url(org_id, project_id, session_id, check_id)
|
|
122
|
+
response = self._request("POST", api_url, json={"content": content})
|
|
123
|
+
|
|
124
|
+
self._raise_for_status(
|
|
125
|
+
response,
|
|
126
|
+
"Failed to create comment in Recce Cloud.",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
data = response.json()
|
|
130
|
+
# Response is wrapped: {"event": {...}}
|
|
131
|
+
return data.get("event", {})
|
|
132
|
+
|
|
133
|
+
def update_comment(
|
|
134
|
+
self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str, content: str
|
|
135
|
+
) -> Dict:
|
|
136
|
+
"""
|
|
137
|
+
Update an existing comment.
|
|
138
|
+
|
|
139
|
+
Only the author or an admin can update a comment.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
org_id: Organization ID
|
|
143
|
+
project_id: Project ID
|
|
144
|
+
session_id: Session ID
|
|
145
|
+
check_id: Check ID
|
|
146
|
+
event_id: Event ID of the comment to update
|
|
147
|
+
content: New comment content
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Updated event dictionary
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
RecceCloudException: If the request fails or user is not authorized
|
|
154
|
+
"""
|
|
155
|
+
api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
|
|
156
|
+
response = self._request("PATCH", api_url, json={"content": content})
|
|
157
|
+
|
|
158
|
+
self._raise_for_status(
|
|
159
|
+
response,
|
|
160
|
+
f"Failed to update comment {event_id} in Recce Cloud.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
data = response.json()
|
|
164
|
+
# Response is wrapped: {"event": {...}}
|
|
165
|
+
return data.get("event", {})
|
|
166
|
+
|
|
167
|
+
def delete_comment(self, org_id: str, project_id: str, session_id: str, check_id: str, event_id: str) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Delete a comment (soft delete).
|
|
170
|
+
|
|
171
|
+
Only the author or an admin can delete a comment.
|
|
172
|
+
The comment will be marked as deleted but remain in the timeline.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
org_id: Organization ID
|
|
176
|
+
project_id: Project ID
|
|
177
|
+
session_id: Session ID
|
|
178
|
+
check_id: Check ID
|
|
179
|
+
event_id: Event ID of the comment to delete
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
RecceCloudException: If the request fails or user is not authorized
|
|
183
|
+
"""
|
|
184
|
+
api_url = f"{self._build_events_url(org_id, project_id, session_id, check_id)}/{event_id}"
|
|
185
|
+
response = self._request("DELETE", api_url)
|
|
186
|
+
|
|
187
|
+
self._raise_for_status(
|
|
188
|
+
response,
|
|
189
|
+
f"Failed to delete comment {event_id} in Recce Cloud.",
|
|
190
|
+
)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recce Cloud API client for check operations.
|
|
3
|
+
|
|
4
|
+
This module provides methods for managing checks (validation operations) in Recce Cloud,
|
|
5
|
+
including CRUD operations for checks within sessions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
|
|
10
|
+
from recce.util.cloud.base import CloudBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChecksCloud(CloudBase):
|
|
14
|
+
"""
|
|
15
|
+
Client for Recce Cloud check operations.
|
|
16
|
+
|
|
17
|
+
Provides methods to list, create, retrieve, update, and delete checks
|
|
18
|
+
within Recce Cloud sessions.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> client = ChecksCloud(token="your-api-token")
|
|
22
|
+
>>> checks = client.list_checks(org_id="org123", project_id="proj456", session_id="sess789")
|
|
23
|
+
>>> check = client.get_check(org_id="org123", project_id="proj456",
|
|
24
|
+
... session_id="sess789", check_id="check001")
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def list_checks(self, org_id: str, project_id: str, session_id: str) -> List[Dict]:
|
|
28
|
+
"""
|
|
29
|
+
List all checks in a session.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
org_id: Organization ID
|
|
33
|
+
project_id: Project ID
|
|
34
|
+
session_id: Session ID
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of check dictionaries
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
RecceCloudException: If the request fails
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> checks = client.list_checks("org123", "proj456", "sess789")
|
|
44
|
+
>>> print(f"Found {len(checks)} checks")
|
|
45
|
+
"""
|
|
46
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks"
|
|
47
|
+
response = self._request("GET", api_url)
|
|
48
|
+
|
|
49
|
+
self._raise_for_status(
|
|
50
|
+
response,
|
|
51
|
+
"Failed to list checks from Recce Cloud.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
data = response.json()
|
|
55
|
+
return data.get("checks", [])
|
|
56
|
+
|
|
57
|
+
def create_check(
|
|
58
|
+
self,
|
|
59
|
+
org_id: str,
|
|
60
|
+
project_id: str,
|
|
61
|
+
session_id: str,
|
|
62
|
+
check_data: Dict,
|
|
63
|
+
) -> Dict:
|
|
64
|
+
"""
|
|
65
|
+
Create a new check in a session.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
org_id: Organization ID
|
|
69
|
+
project_id: Project ID
|
|
70
|
+
session_id: Session ID
|
|
71
|
+
check_data: Check data to create (should include check type, params, etc.)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Created check dictionary
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
RecceCloudException: If the request fails
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> check_data = {
|
|
81
|
+
... "name": "Schema Check",
|
|
82
|
+
... "type": "schema_diff",
|
|
83
|
+
... "params": {"model": "customers"}
|
|
84
|
+
... }
|
|
85
|
+
>>> check = client.create_check("org123", "proj456", "sess789", check_data)
|
|
86
|
+
>>> print(f"Created check with ID: {check['id']}")
|
|
87
|
+
"""
|
|
88
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks"
|
|
89
|
+
response = self._request("POST", api_url, json=check_data)
|
|
90
|
+
|
|
91
|
+
self._raise_for_status(
|
|
92
|
+
response,
|
|
93
|
+
"Failed to create check in Recce Cloud.",
|
|
94
|
+
)
|
|
95
|
+
data = response.json()
|
|
96
|
+
return data.get("check")
|
|
97
|
+
|
|
98
|
+
def get_check(
|
|
99
|
+
self,
|
|
100
|
+
org_id: str,
|
|
101
|
+
project_id: str,
|
|
102
|
+
session_id: str,
|
|
103
|
+
check_id: str,
|
|
104
|
+
) -> Dict:
|
|
105
|
+
"""
|
|
106
|
+
Get a specific check by ID.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
org_id: Organization ID
|
|
110
|
+
project_id: Project ID
|
|
111
|
+
session_id: Session ID
|
|
112
|
+
check_id: Check ID
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Check dictionary
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
RecceCloudException: If the request fails or check not found
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> check = client.get_check("org123", "proj456", "sess789", "check001")
|
|
122
|
+
>>> print(f"Check name: {check['name']}")
|
|
123
|
+
>>> print(f"Check status: {check['status']}")
|
|
124
|
+
"""
|
|
125
|
+
api_url = (
|
|
126
|
+
f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
|
|
127
|
+
)
|
|
128
|
+
response = self._request("GET", api_url)
|
|
129
|
+
|
|
130
|
+
self._raise_for_status(
|
|
131
|
+
response,
|
|
132
|
+
f"Failed to get check {check_id} from Recce Cloud.",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
data = response.json()
|
|
136
|
+
return data.get("check", {})
|
|
137
|
+
|
|
138
|
+
def update_check(
|
|
139
|
+
self,
|
|
140
|
+
org_id: str,
|
|
141
|
+
project_id: str,
|
|
142
|
+
session_id: str,
|
|
143
|
+
check_id: str,
|
|
144
|
+
check_data: Dict,
|
|
145
|
+
) -> Dict:
|
|
146
|
+
"""
|
|
147
|
+
Update an existing check.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
org_id: Organization ID
|
|
151
|
+
project_id: Project ID
|
|
152
|
+
session_id: Session ID
|
|
153
|
+
check_id: Check ID
|
|
154
|
+
check_data: Updated check data (partial updates supported)
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Updated check dictionary
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
RecceCloudException: If the request fails
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
>>> update_data = {
|
|
164
|
+
... "status": "approved",
|
|
165
|
+
... "notes": "Validated successfully"
|
|
166
|
+
... }
|
|
167
|
+
>>> check = client.update_check("org123", "proj456", "sess789", "check001", update_data)
|
|
168
|
+
>>> print(f"Updated check status: {check['status']}")
|
|
169
|
+
"""
|
|
170
|
+
api_url = (
|
|
171
|
+
f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
|
|
172
|
+
)
|
|
173
|
+
response = self._request("PATCH", api_url, json=check_data)
|
|
174
|
+
|
|
175
|
+
self._raise_for_status(
|
|
176
|
+
response,
|
|
177
|
+
f"Failed to update check {check_id} in Recce Cloud.",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
data = response.json()
|
|
181
|
+
return data.get("check", {})
|
|
182
|
+
|
|
183
|
+
def delete_check(
|
|
184
|
+
self,
|
|
185
|
+
org_id: str,
|
|
186
|
+
project_id: str,
|
|
187
|
+
session_id: str,
|
|
188
|
+
check_id: str,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Delete a check.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
org_id: Organization ID
|
|
195
|
+
project_id: Project ID
|
|
196
|
+
session_id: Session ID
|
|
197
|
+
check_id: Check ID
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
RecceCloudException: If the request fails
|
|
201
|
+
|
|
202
|
+
Example:
|
|
203
|
+
>>> client.delete_check("org123", "proj456", "sess789", "check001")
|
|
204
|
+
>>> print("Check deleted successfully")
|
|
205
|
+
"""
|
|
206
|
+
api_url = (
|
|
207
|
+
f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/checks/{check_id}"
|
|
208
|
+
)
|
|
209
|
+
response = self._request("DELETE", api_url)
|
|
210
|
+
|
|
211
|
+
# DELETE typically returns 204 No Content on success
|
|
212
|
+
if response.status_code not in (200, 204):
|
|
213
|
+
self._raise_for_status(
|
|
214
|
+
response,
|
|
215
|
+
f"Failed to delete check {check_id} from Recce Cloud.",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def create_preset_check(self, org_id: str, project_id: str, check_data: Dict):
|
|
219
|
+
"""
|
|
220
|
+
Create a preset check from an existing check.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
org_id: Organization ID
|
|
224
|
+
project_id: Project ID
|
|
225
|
+
check_data: Check data including name, description, type, params, view_options, and order_index
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Created preset check dictionary
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
RecceCloudException: If the request fails
|
|
232
|
+
"""
|
|
233
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/preset-checks"
|
|
234
|
+
response = self._request("POST", api_url, json=check_data)
|
|
235
|
+
|
|
236
|
+
self._raise_for_status(
|
|
237
|
+
response,
|
|
238
|
+
"Failed to create preset check in Recce Cloud.",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
data = response.json()
|
|
242
|
+
return data.get("presetCheck", {})
|
recce/util/io.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import os
|
|
3
3
|
import tempfile
|
|
4
|
-
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
5
5
|
from enum import Enum
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class SupportedFileTypes(Enum):
|
|
9
|
-
FILE =
|
|
10
|
-
GZIP =
|
|
11
|
-
ZIP =
|
|
9
|
+
FILE = "file"
|
|
10
|
+
GZIP = "gzip"
|
|
11
|
+
ZIP = "zip"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def file_io_factory(file_type: SupportedFileTypes):
|
|
@@ -19,7 +19,7 @@ def file_io_factory(file_type: SupportedFileTypes):
|
|
|
19
19
|
elif file_type == SupportedFileTypes.ZIP:
|
|
20
20
|
return ZipFileIO
|
|
21
21
|
else:
|
|
22
|
-
raise ValueError(f
|
|
22
|
+
raise ValueError(f"Unsupported file type: {file_type}")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class AbstractFileIO(metaclass=ABCMeta):
|
|
@@ -37,24 +37,24 @@ class AbstractFileIO(metaclass=ABCMeta):
|
|
|
37
37
|
class FileIO(AbstractFileIO, ABC):
|
|
38
38
|
@staticmethod
|
|
39
39
|
def write(path: str, data: str, **kwargs):
|
|
40
|
-
with open(path,
|
|
40
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
41
41
|
f.write(data)
|
|
42
42
|
|
|
43
43
|
@staticmethod
|
|
44
44
|
def read(path: str, **kwargs) -> str:
|
|
45
|
-
with open(path,
|
|
45
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
46
46
|
return f.read()
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class GzipFileIO(AbstractFileIO, ABC):
|
|
50
50
|
@staticmethod
|
|
51
51
|
def write(path: str, data: str, **kwargs):
|
|
52
|
-
with gzip.open(path,
|
|
52
|
+
with gzip.open(path, "wt") as f:
|
|
53
53
|
f.write(data)
|
|
54
54
|
|
|
55
55
|
@staticmethod
|
|
56
56
|
def read(path: str, **kwargs) -> str:
|
|
57
|
-
with gzip.open(path,
|
|
57
|
+
with gzip.open(path, "rt") as f:
|
|
58
58
|
return f.read()
|
|
59
59
|
|
|
60
60
|
@staticmethod
|
|
@@ -68,18 +68,22 @@ class ZipFileIO(AbstractFileIO, ABC):
|
|
|
68
68
|
def _is_pyminizip_installed():
|
|
69
69
|
try:
|
|
70
70
|
import pyminizip
|
|
71
|
+
|
|
72
|
+
# Use the module to avoid F401
|
|
73
|
+
return pyminizip is not None
|
|
71
74
|
except ImportError:
|
|
72
|
-
raise ImportError(
|
|
75
|
+
raise ImportError("pyminizip is not installed. Please install it using `pip install pyminizip`")
|
|
73
76
|
|
|
74
77
|
@staticmethod
|
|
75
78
|
def read(path: str, **kwargs) -> str:
|
|
76
79
|
ZipFileIO._is_pyminizip_installed()
|
|
77
80
|
import pyminizip
|
|
81
|
+
|
|
78
82
|
cwd = os.getcwd()
|
|
79
|
-
password = kwargs.get(
|
|
80
|
-
zip_dir_name = kwargs.get(
|
|
83
|
+
password = kwargs.get("password")
|
|
84
|
+
zip_dir_name = kwargs.get("zip_dir_name")
|
|
81
85
|
if zip_dir_name is None:
|
|
82
|
-
raise ValueError(
|
|
86
|
+
raise ValueError("zip_dir_name is required for zipping")
|
|
83
87
|
|
|
84
88
|
try:
|
|
85
89
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
@@ -88,7 +92,7 @@ class ZipFileIO(AbstractFileIO, ABC):
|
|
|
88
92
|
content = FileIO.read(tmp_file)
|
|
89
93
|
except Exception as e:
|
|
90
94
|
error_msg = str(e)
|
|
91
|
-
if
|
|
95
|
+
if "-3" in error_msg:
|
|
92
96
|
raise Exception("Invalid password to uncompress state file.")
|
|
93
97
|
raise Exception(f"Failed to uncompress state file: {error_msg}")
|
|
94
98
|
finally:
|
|
@@ -100,11 +104,12 @@ class ZipFileIO(AbstractFileIO, ABC):
|
|
|
100
104
|
def write(path: str, data: str, **kwargs):
|
|
101
105
|
ZipFileIO._is_pyminizip_installed()
|
|
102
106
|
import pyminizip
|
|
107
|
+
|
|
103
108
|
cwd = os.getcwd()
|
|
104
|
-
password = kwargs.get(
|
|
105
|
-
zip_dir_name = kwargs.get(
|
|
109
|
+
password = kwargs.get("password")
|
|
110
|
+
zip_dir_name = kwargs.get("zip_dir_name")
|
|
106
111
|
if zip_dir_name is None:
|
|
107
|
-
raise ValueError(
|
|
112
|
+
raise ValueError("zip_dir_name is required for zipping")
|
|
108
113
|
|
|
109
114
|
try:
|
|
110
115
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
recce/util/lineage.py
CHANGED
|
@@ -1,34 +1,83 @@
|
|
|
1
|
-
|
|
1
|
+
from typing import Dict, Iterable, Set, Tuple
|
|
2
|
+
|
|
3
|
+
from recce.models.types import CllColumn, CllNode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def find_upstream(node_ids: Iterable, parent_map):
|
|
2
7
|
visited = set()
|
|
3
8
|
upstream = set()
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
stack = list(node_ids)
|
|
11
|
+
while stack:
|
|
12
|
+
current = stack.pop()
|
|
6
13
|
if current in visited:
|
|
7
|
-
|
|
14
|
+
continue
|
|
8
15
|
visited.add(current)
|
|
9
|
-
|
|
10
16
|
parents = parent_map.get(current, [])
|
|
11
17
|
for parent in parents:
|
|
12
|
-
upstream
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
dfs(node)
|
|
18
|
+
if parent not in upstream:
|
|
19
|
+
upstream.add(parent)
|
|
20
|
+
stack.append(parent)
|
|
16
21
|
return upstream
|
|
17
22
|
|
|
18
23
|
|
|
19
|
-
def find_downstream(
|
|
24
|
+
def find_downstream(node_ids: Iterable, child_map):
|
|
20
25
|
visited = set()
|
|
21
26
|
downstream = set()
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
stack = list(node_ids)
|
|
29
|
+
while stack:
|
|
30
|
+
current = stack.pop()
|
|
24
31
|
if current in visited:
|
|
25
|
-
|
|
32
|
+
continue
|
|
26
33
|
visited.add(current)
|
|
27
|
-
|
|
28
34
|
children = child_map.get(current, [])
|
|
29
35
|
for child in children:
|
|
30
|
-
downstream
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
dfs(node)
|
|
36
|
+
if child not in downstream:
|
|
37
|
+
downstream.add(child)
|
|
38
|
+
stack.append(child)
|
|
34
39
|
return downstream
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_column_dependencies(node_column_id: str, parent_map: Dict, child_map: Dict) -> Tuple[Set, Set]:
|
|
43
|
+
upstream_cols = find_upstream([node_column_id], parent_map)
|
|
44
|
+
downstream_cols = find_downstream([node_column_id], child_map)
|
|
45
|
+
return upstream_cols, downstream_cols
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def filter_lineage_vertices(
|
|
49
|
+
lineage_nodes: Dict[str, CllNode], lineage_columns: Dict[str, CllColumn], relevant_columns: Set[str]
|
|
50
|
+
) -> Tuple[Dict[str, CllNode], Dict[str, CllColumn]]:
|
|
51
|
+
nodes = {}
|
|
52
|
+
columns = {}
|
|
53
|
+
|
|
54
|
+
for node_id, node in lineage_nodes.items():
|
|
55
|
+
if node_id in relevant_columns:
|
|
56
|
+
nodes[node_id] = node
|
|
57
|
+
|
|
58
|
+
for col_id, column in lineage_columns.items():
|
|
59
|
+
if col_id in relevant_columns:
|
|
60
|
+
columns[col_id] = column
|
|
61
|
+
|
|
62
|
+
return nodes, columns
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def filter_dependency_maps(
|
|
66
|
+
parent_map: Dict, child_map: Dict, relevant_ids: Set
|
|
67
|
+
) -> Tuple[Dict[str, Set], Dict[str, Set]]:
|
|
68
|
+
p_map = {}
|
|
69
|
+
c_map = {}
|
|
70
|
+
for node_id, parents in parent_map.items():
|
|
71
|
+
if node_id in relevant_ids:
|
|
72
|
+
p_map[node_id] = {p for p in parents if p in relevant_ids}
|
|
73
|
+
|
|
74
|
+
for node_id, children in child_map.items():
|
|
75
|
+
if node_id in relevant_ids:
|
|
76
|
+
c_map[node_id] = {c for c in children if c in relevant_ids}
|
|
77
|
+
|
|
78
|
+
return p_map, c_map
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_column_key(node_id: str, column_name: str) -> str:
|
|
82
|
+
"""Build a unique column key from node name and column name."""
|
|
83
|
+
return f"{node_id}_{column_name}"
|
recce/util/logger.py
CHANGED
|
@@ -16,7 +16,7 @@ class CustomFormatter(logging.Formatter):
|
|
|
16
16
|
logging.INFO: green + format + reset,
|
|
17
17
|
logging.WARNING: yellow + format + reset,
|
|
18
18
|
logging.ERROR: red + format + reset,
|
|
19
|
-
logging.CRITICAL: bold_red + format + reset
|
|
19
|
+
logging.CRITICAL: bold_red + format + reset,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
def format(self, record):
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from recce.util.recce_cloud import (
|
|
5
|
+
get_recce_cloud_onboarding_state,
|
|
6
|
+
set_recce_cloud_onboarding_state,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OnboardingState(Enum):
|
|
11
|
+
SIGNUP_SUCCESSFUL = "signup_successful"
|
|
12
|
+
LAUNCHED_WITH_TOKEN = "launched_with_token"
|
|
13
|
+
CONFIGURE_TWO_ENV = "configure_two_env"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def update_onboarding_state(api_token: Union[str, bool, None], is_single_env: bool) -> OnboardingState:
|
|
17
|
+
if api_token:
|
|
18
|
+
# existing onboarding_state values -> new, launched, launched_with_two_envs
|
|
19
|
+
# new -> launched
|
|
20
|
+
# new -> launched_with_two_envs
|
|
21
|
+
# launched -> launched_with_two_envs
|
|
22
|
+
cloud_onboarding_state = get_recce_cloud_onboarding_state(api_token)
|
|
23
|
+
|
|
24
|
+
if cloud_onboarding_state == OnboardingState.SIGNUP_SUCCESSFUL.value:
|
|
25
|
+
# User has an API Token and is a "new" user
|
|
26
|
+
if is_single_env:
|
|
27
|
+
# Mark the onboarding state as "launched" if the user is new
|
|
28
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.LAUNCHED_WITH_TOKEN.value)
|
|
29
|
+
return OnboardingState.LAUNCHED_WITH_TOKEN
|
|
30
|
+
else:
|
|
31
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.CONFIGURE_TWO_ENV.value)
|
|
32
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
33
|
+
elif cloud_onboarding_state == OnboardingState.LAUNCHED_WITH_TOKEN.value:
|
|
34
|
+
# User has an API Token and has Two Environments
|
|
35
|
+
if is_single_env:
|
|
36
|
+
# Just return the current state
|
|
37
|
+
return OnboardingState.LAUNCHED_WITH_TOKEN
|
|
38
|
+
else:
|
|
39
|
+
set_recce_cloud_onboarding_state(api_token, OnboardingState.CONFIGURE_TWO_ENV.value)
|
|
40
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
41
|
+
elif cloud_onboarding_state == OnboardingState.CONFIGURE_TWO_ENV.value:
|
|
42
|
+
# Just return the current state
|
|
43
|
+
return OnboardingState.CONFIGURE_TWO_ENV
|
|
44
|
+
|
|
45
|
+
return OnboardingState.SIGNUP_SUCCESSFUL
|