Skip to content

Commit 927394d

Browse files
authored
Merge pull request #235 from TS-sumeet-jain/dev
Fix for Archiver, Searchable & User management
2 parents 9e46088 + c0fddc9 commit 927394d

File tree

7 files changed

+94
-24
lines changed

7 files changed

+94
-24
lines changed

cs_tools/__project__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.6.3"
1+
__version__ = "1.6.4"
22
__docs__ = "https://thoughtspot.github.io/cs_tools/"
33
__repo__ = "https://github.com/thoughtspot/cs_tools"
44
__help__ = f"{__repo__}/discussions/"

cs_tools/api/client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,13 @@ def groups_search_v1(self, **options: Any) -> Awaitable[httpx.Response]: # noqa
436436
"""Get a list of ThoughtSpot groups with v1 endpoint."""
437437
return self.get("callosum/v1/tspublic/v1/group")
438438

439+
@pydantic.validate_call(validate_return=True, config=validators.METHOD_CONFIG)
440+
@_transport.CachePolicy.mark_cacheable
441+
def group_list_users(self, group_guid: _types.ObjectIdentifier, **options: Any) -> Awaitable[httpx.Response]: # noqa: ARG002
442+
"""Get a list of ThoughtSpot users in a group with v1 endpoint."""
443+
r = self.get(f"callosum/v1/tspublic/v1/group/{group_guid}/users")
444+
return r
445+
439446
@pydantic.validate_call(validate_return=True, config=validators.METHOD_CONFIG)
440447
def groups_create(self, **options: Any) -> Awaitable[httpx.Response]:
441448
"""Create a ThoughtSpot group."""

cs_tools/cli/tools/archiver/app.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ def _tick_tock(task: px.WorkTask) -> None:
4141
task.advance(step=1)
4242

4343

44+
def get_group_users(guids: list[_types.ObjectIdentifier], users_profiles: list[dict]):
45+
users = []
46+
for guid in guids:
47+
for user in users_profiles:
48+
if guid in user["assignedGroups"]:
49+
users.append(user["header"])
50+
51+
if guid in user["inheritedGroups"]:
52+
users.append(user["header"])
53+
54+
return users
55+
56+
4457
app = AsyncTyper(
4558
help="""
4659
Manage stale answers and liveboards within your platform.
@@ -156,7 +169,7 @@ def identify(
156169

157170
SEARCH_TOKENS = (
158171
# SELECT TS: BI ACTIVITY ON ANY ACTIVITY WITH A QUERY
159-
"[query text] != '{null}' "
172+
# "[query text] != '{null}' "
160173
# FILTER OUT AD-HOC SEARCH
161174
"[user action] != [user action].answer_unsaved "
162175
# FILTER OUT POTENTIAL DATA QUALITY ISSUES ? (just here for safety~)
@@ -187,22 +200,67 @@ def identify(
187200
if only_groups or ignore_groups:
188201
c = ts.api.groups_search_v1()
189202
r = utils.run_sync(c)
190-
_ = r.json()
191-
192-
# c = workflows.paginator(ts.api.groups_search, record_size=150_000, timeout=60 * 15)
193-
# d = utils.run_sync(c)
203+
d = r.json()
194204

205+
# Inline with V1 Group API.
195206
all_groups = [
196207
{
197-
"guid": group["id"],
198-
"name": group["name"],
199-
"users": group["users"],
208+
"guid": group["header"]["id"],
209+
"name": group["header"]["name"],
200210
}
201211
for group in d
202212
]
203213

204-
only_groups = [group["guid"] for group in all_groups if group["name"] in (only_groups or [])] # type: ignore[assignment]
205-
ignore_groups = [group["guid"] for group in all_groups if group["name"] in (ignore_groups or [])] # type: ignore[assignment]
214+
# c = workflows.paginator(ts.api.groups_search, record_size=150_000, timeout=60 * 15)
215+
# d = utils.run_sync(c)
216+
217+
# all_groups = [
218+
# {
219+
# "guid": group["id"],
220+
# "name": group["name"],
221+
# "users": group["users"],
222+
# }
223+
# for group in d
224+
# ]
225+
226+
only_groups_lst = [group["guid"] for group in all_groups if group["name"] in (only_groups or [])] # type: ignore[assignment]
227+
ignore_groups_lst = [group["guid"] for group in all_groups if group["name"] in (ignore_groups or [])] # type: ignore[assignment]
228+
229+
if only_groups is not None:
230+
users_profiles: list = []
231+
for guid in only_groups_lst:
232+
c = ts.api.group_list_users(group_guid=guid)
233+
r = r = utils.run_sync(c)
234+
users = r.json()
235+
if not users:
236+
# Exception needs to be redone
237+
info = {
238+
"reason": "Group names are case sensitive. "
239+
+ "You can find a group's 'Group Name' in the Admin panel.",
240+
"mitigation": "Verify the name and try again.",
241+
"type": "Group",
242+
}
243+
raise Exception(str(info)) from None
244+
users_profiles.extend(users)
245+
users_only = [user["id"] for user in get_group_users(only_groups_lst, users_profiles)]
246+
247+
if ignore_groups is not None:
248+
users_profiles: list = []
249+
for guid in ignore_groups_lst:
250+
c = ts.api.group_list_users(group_guid=guid)
251+
r = utils.run_sync(c)
252+
users = r.json()
253+
if not users:
254+
info = {
255+
"reason": "Group names are case sensitive. "
256+
+ "You can find a group's 'Group Name' in the Admin panel.",
257+
"mitigation": "Verify the name and try again.",
258+
"type": "Group",
259+
}
260+
raise Exception(str(info)) from None
261+
users_profiles.extend(users)
262+
263+
users_ignore = [user["id"] for user in get_group_users(ignore_groups_lst, users_profiles)]
206264

207265
if ignore_tags:
208266
c = workflows.metadata.fetch_all(metadata_types=["TAG"], http=ts.api)
@@ -259,18 +317,18 @@ def identify(
259317
# CHECK: THE AUTHOR IS A MEMBER OF GROUPS WHOSE CONTENT SHOULD NEEDS TO INCLUDED.
260318
if only_groups is not None:
261319
assert isinstance(only_groups, list), "Only Groups wasn't properly transformed to an array<GUID>."
262-
checks.append(metadata_object["author_guid"] in only_groups)
320+
checks.append(metadata_object["author_guid"] in users_only)
263321

264322
# CHECK: THE AUTHOR IS NOT A MEMBER OF GROUPS WHOSE CONTENT SHOULD BE IGNORED.
265323
if ignore_groups is not None:
266324
assert isinstance(
267325
ignore_groups, list
268326
), "Ignore Groups wasn't properly transformed to an array<GUID>."
269-
checks.append(metadata_object["author_guid"] not in ignore_groups)
327+
checks.append(metadata_object["author_guid"] not in users_ignore)
270328

271329
if ignore_tags is not None:
272330
assert isinstance(ignore_tags, list), "Ignore Tags wasn't properly transformed to an array<GUID>."
273-
checks.append(any(t["id"] not in ignore_tags for t in metadata_object["tags"]))
331+
checks.append(not any(t["id"] in ignore_tags for t in metadata_object["tags"]))
274332

275333
if all(checks):
276334
filtered.append(metadata_object)

cs_tools/cli/tools/searchable/app.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,11 @@ def metadata(
428428
tracker["ORGS_COUNT"].start()
429429

430430
# LOOP THROUGH EACH ORG COLLECTING DATA
431-
primary_org_done = False
431+
if org_override is not None:
432+
collect_info = True
433+
else:
434+
collect_info = False
435+
432436
for org in orgs:
433437
tracker.title = f"Fetching Data in [fg-secondary]{org['name']}[/] (Org {org['id']})"
434438
seen_guids: dict[_types.APIObjectType, set[_types.GUID]] = collections.defaultdict(set)
@@ -477,7 +481,7 @@ def metadata(
477481
d = api_transformer.to_group_privilege(data=_, cluster=CLUSTER_UUID)
478482
temp.dump(models.GroupPrivilege.__tablename__, data=d)
479483

480-
if org["id"] == 0 and not primary_org_done:
484+
if org["id"] == 0 or collect_info:
481485
with tracker["TS_USER"]:
482486
c = workflows.paginator(ts.api.users_search, record_size=5_000, timeout=60 * 15)
483487
_ = utils.run_sync(c)
@@ -493,7 +497,7 @@ def metadata(
493497
# DUMP USER->GROUP_MEMBERSHIP DATA
494498
d = api_transformer.ts_group_membership(data=_, cluster=CLUSTER_UUID)
495499
temp.dump(models.GroupMembership.__tablename__, data=d)
496-
primary_org_done = True
500+
collect_info = False
497501
elif org["id"] != 0:
498502
log.info(f"Skipping USER data fetch for non-primary org (ID: {org['id']}) as it was already fetched.")
499503

cs_tools/cli/tools/user-management/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def delete(
244244
with tracker["DELETE"] as this_task:
245245
this_task.total = len(user_identifiers)
246246

247-
users_to_delete: set[_types.GUID] = {metadata_object["guid"] for metadata_object in user_identifiers}
247+
users_to_delete: set[_types.GUID] = user_identifiers
248248
delete_attempts = collections.defaultdict(int)
249249

250250
async def _delete_and_advance(guid: _types.GUID) -> None:

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ classifiers = [
3535
dependencies = [
3636
# dependencies here are listed for cs_tools the library (ie. import cs_tools)
3737
"aiosqlite",
38+
"click == 8.1.7",
3839
"thoughtspot-tml",
3940
"awesomeversion",
4041
"httpx >= 0.27.0",
4142
"pydantic >= 2.6.4",
4243
"pydantic-settings",
4344
"email-validator",
44-
"rich >= 13.7.1",
45+
"rich == 13.7.1",
4546
"sqlmodel >= 0.0.16",
4647
"tenacity",
4748
"toml",

uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)