pvw-cli 1.0.11__py3-none-any.whl → 1.0.14__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 pvw-cli might be problematic. Click here for more details.
- purviewcli/__init__.py +2 -2
- purviewcli/cli/account.py +24 -24
- purviewcli/cli/cli.py +16 -16
- purviewcli/cli/collections.py +24 -24
- purviewcli/cli/domain.py +8 -8
- purviewcli/cli/entity.py +193 -193
- purviewcli/cli/glossary.py +29 -29
- purviewcli/cli/health.py +2 -2
- purviewcli/cli/insight.py +7 -7
- purviewcli/cli/lineage.py +11 -11
- purviewcli/cli/unified_catalog.py +707 -36
- purviewcli/cli/workflow.py +43 -43
- purviewcli/client/_unified_catalog.py +97 -42
- purviewcli/client/api_client.py +9 -1
- {pvw_cli-1.0.11.dist-info → pvw_cli-1.0.14.dist-info}/METADATA +378 -58
- {pvw_cli-1.0.11.dist-info → pvw_cli-1.0.14.dist-info}/RECORD +19 -19
- {pvw_cli-1.0.11.dist-info → pvw_cli-1.0.14.dist-info}/WHEEL +0 -0
- {pvw_cli-1.0.11.dist-info → pvw_cli-1.0.14.dist-info}/entry_points.txt +0 -0
- {pvw_cli-1.0.11.dist-info → pvw_cli-1.0.14.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ import csv
|
|
|
8
8
|
import json
|
|
9
9
|
import tempfile
|
|
10
10
|
import os
|
|
11
|
+
import time
|
|
11
12
|
from rich.console import Console
|
|
12
13
|
from rich.table import Table
|
|
13
14
|
from rich.text import Text
|
|
@@ -51,8 +52,7 @@ def domain():
|
|
|
51
52
|
"--type",
|
|
52
53
|
required=False,
|
|
53
54
|
default="FunctionalUnit",
|
|
54
|
-
|
|
55
|
-
help="Type of governance domain",
|
|
55
|
+
help="Type of governance domain (default: FunctionalUnit). Note: UC API currently only accepts 'FunctionalUnit'.",
|
|
56
56
|
)
|
|
57
57
|
@click.option(
|
|
58
58
|
"--owner-id",
|
|
@@ -67,19 +67,46 @@ def domain():
|
|
|
67
67
|
type=click.Choice(["Draft", "Published", "Archived"]),
|
|
68
68
|
help="Status of the governance domain",
|
|
69
69
|
)
|
|
70
|
-
|
|
70
|
+
@click.option(
|
|
71
|
+
"--parent-id",
|
|
72
|
+
required=False,
|
|
73
|
+
help="Parent governance domain ID (create as subdomain under this domain)",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"--payload-file",
|
|
77
|
+
required=False,
|
|
78
|
+
type=click.Path(exists=True),
|
|
79
|
+
help="Optional JSON payload file to use for creating the domain (overrides flags if provided)",
|
|
80
|
+
)
|
|
81
|
+
def create(name, description, type, owner_id, status, parent_id, payload_file):
|
|
71
82
|
"""Create a new governance domain."""
|
|
72
83
|
try:
|
|
73
84
|
client = UnifiedCatalogClient()
|
|
74
85
|
|
|
75
86
|
# Build args dictionary in Purview CLI format
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
# If payload-file is provided we will let the client read the file directly
|
|
88
|
+
# otherwise build args from individual flags.
|
|
89
|
+
args = {}
|
|
90
|
+
# Note: click will pass None for owner_id if not provided, but multiple=True returns ()
|
|
91
|
+
# We'll only include values if payload-file not used.
|
|
92
|
+
if locals().get('payload_file'):
|
|
93
|
+
args = {"--payloadFile": locals().get('payload_file')}
|
|
94
|
+
else:
|
|
95
|
+
args = {
|
|
96
|
+
"--name": [name],
|
|
97
|
+
"--description": [description],
|
|
98
|
+
"--type": [type],
|
|
99
|
+
"--status": [status],
|
|
100
|
+
}
|
|
101
|
+
if owner_id:
|
|
102
|
+
args["--owner-id"] = list(owner_id)
|
|
103
|
+
# include parent id if provided
|
|
104
|
+
parent_id = locals().get('parent_id')
|
|
105
|
+
if parent_id:
|
|
106
|
+
# use a consistent arg name for client lookup
|
|
107
|
+
args["--parent-domain-id"] = [parent_id]
|
|
108
|
+
|
|
109
|
+
# Call the client to create the governance domain
|
|
83
110
|
result = client.create_governance_domain(args)
|
|
84
111
|
|
|
85
112
|
if not result:
|
|
@@ -89,7 +116,7 @@ def create(name, description, type, owner_id, status):
|
|
|
89
116
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
90
117
|
return
|
|
91
118
|
|
|
92
|
-
console.print(f"[green]
|
|
119
|
+
console.print(f"[green] SUCCESS:[/green] Created governance domain '{name}'")
|
|
93
120
|
console.print(json.dumps(result, indent=2))
|
|
94
121
|
|
|
95
122
|
except Exception as e:
|
|
@@ -97,9 +124,20 @@ def create(name, description, type, owner_id, status):
|
|
|
97
124
|
|
|
98
125
|
|
|
99
126
|
@domain.command(name="list")
|
|
100
|
-
@click.option(
|
|
101
|
-
|
|
102
|
-
"""
|
|
127
|
+
@click.option(
|
|
128
|
+
"--output",
|
|
129
|
+
type=click.Choice(["table", "json", "jsonc"]),
|
|
130
|
+
default="table",
|
|
131
|
+
help="Output format: table (default, formatted), json (plain, parseable), jsonc (colored JSON)"
|
|
132
|
+
)
|
|
133
|
+
def list_domains(output):
|
|
134
|
+
"""List all governance domains.
|
|
135
|
+
|
|
136
|
+
Output formats:
|
|
137
|
+
- table: Formatted table output with Rich (default)
|
|
138
|
+
- json: Plain JSON for scripting (use with PowerShell ConvertFrom-Json)
|
|
139
|
+
- jsonc: Colored JSON with syntax highlighting for viewing
|
|
140
|
+
"""
|
|
103
141
|
try:
|
|
104
142
|
client = UnifiedCatalogClient()
|
|
105
143
|
args = {} # No arguments needed for list operation
|
|
@@ -121,8 +159,13 @@ def list_domains(output_json):
|
|
|
121
159
|
console.print("[yellow]No governance domains found.[/yellow]")
|
|
122
160
|
return
|
|
123
161
|
|
|
124
|
-
#
|
|
125
|
-
if
|
|
162
|
+
# Handle output format
|
|
163
|
+
if output == "json":
|
|
164
|
+
# Plain JSON for scripting (PowerShell compatible)
|
|
165
|
+
print(json.dumps(domains, indent=2))
|
|
166
|
+
return
|
|
167
|
+
elif output == "jsonc":
|
|
168
|
+
# Colored JSON for viewing
|
|
126
169
|
_format_json_output(domains)
|
|
127
170
|
return
|
|
128
171
|
|
|
@@ -248,7 +291,7 @@ def create(
|
|
|
248
291
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
249
292
|
return
|
|
250
293
|
|
|
251
|
-
console.print(f"[green]
|
|
294
|
+
console.print(f"[green] SUCCESS:[/green] Created data product '{name}'")
|
|
252
295
|
console.print(json.dumps(result, indent=2))
|
|
253
296
|
|
|
254
297
|
except Exception as e:
|
|
@@ -418,7 +461,7 @@ def update(
|
|
|
418
461
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
419
462
|
return
|
|
420
463
|
|
|
421
|
-
console.print(f"[green]
|
|
464
|
+
console.print(f"[green] SUCCESS:[/green] Updated data product '{product_id}'")
|
|
422
465
|
console.print(json.dumps(result, indent=2))
|
|
423
466
|
|
|
424
467
|
except Exception as e:
|
|
@@ -446,11 +489,11 @@ def delete(product_id, yes):
|
|
|
446
489
|
|
|
447
490
|
# DELETE operations may return empty response on success
|
|
448
491
|
if result is None or (isinstance(result, dict) and not result.get("error")):
|
|
449
|
-
console.print(f"[green]
|
|
492
|
+
console.print(f"[green] SUCCESS:[/green] Deleted data product '{product_id}'")
|
|
450
493
|
elif isinstance(result, dict) and "error" in result:
|
|
451
494
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
452
495
|
else:
|
|
453
|
-
console.print(f"[green]
|
|
496
|
+
console.print(f"[green] SUCCESS:[/green] Deleted data product '{product_id}'")
|
|
454
497
|
if result:
|
|
455
498
|
console.print(json.dumps(result, indent=2))
|
|
456
499
|
|
|
@@ -553,7 +596,7 @@ def create_glossary(name, description, domain_id):
|
|
|
553
596
|
return
|
|
554
597
|
|
|
555
598
|
guid = result.get("guid") if isinstance(result, dict) else None
|
|
556
|
-
console.print(f"[green]
|
|
599
|
+
console.print(f"[green] SUCCESS:[/green] Created glossary '{name}'")
|
|
557
600
|
if guid:
|
|
558
601
|
console.print(f"[cyan]GUID:[/cyan] {guid}")
|
|
559
602
|
console.print(f"\n[dim]Use this GUID: --glossary-guid {guid}[/dim]")
|
|
@@ -638,13 +681,13 @@ def create_glossaries_for_domains():
|
|
|
638
681
|
guid = result.get("guid") if isinstance(result, dict) else None
|
|
639
682
|
|
|
640
683
|
if guid:
|
|
641
|
-
console.print(f"[green]
|
|
684
|
+
console.print(f"[green] Created:[/green] {glossary_name} (GUID: {guid})")
|
|
642
685
|
created_count += 1
|
|
643
686
|
else:
|
|
644
|
-
console.print(f"[yellow]
|
|
687
|
+
console.print(f"[yellow] Created {glossary_name} but no GUID returned[/yellow]")
|
|
645
688
|
|
|
646
689
|
except Exception as e:
|
|
647
|
-
console.print(f"[red]
|
|
690
|
+
console.print(f"[red] Failed to create {glossary_name}:[/red] {str(e)}")
|
|
648
691
|
|
|
649
692
|
console.print(f"\n[cyan]Created {created_count} new glossaries[/cyan]")
|
|
650
693
|
console.print("[dim]Run 'pvw uc glossary list' to see all glossaries[/dim]")
|
|
@@ -730,7 +773,7 @@ def verify_glossary_links():
|
|
|
730
773
|
domain_id[:8] + "...",
|
|
731
774
|
glossary_info["name"],
|
|
732
775
|
glossary_info["guid"][:8] + "...",
|
|
733
|
-
"[green]
|
|
776
|
+
"[green] Linked[/green]"
|
|
734
777
|
)
|
|
735
778
|
linked_count += 1
|
|
736
779
|
else:
|
|
@@ -739,7 +782,7 @@ def verify_glossary_links():
|
|
|
739
782
|
domain_id[:8] + "...",
|
|
740
783
|
"[dim]No glossary[/dim]",
|
|
741
784
|
"[dim]N/A[/dim]",
|
|
742
|
-
"[yellow]
|
|
785
|
+
"[yellow] Not Linked[/yellow]"
|
|
743
786
|
)
|
|
744
787
|
unlinked_count += 1
|
|
745
788
|
|
|
@@ -822,7 +865,7 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
|
|
|
822
865
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
823
866
|
return
|
|
824
867
|
|
|
825
|
-
console.print(f"[green]
|
|
868
|
+
console.print(f"[green] SUCCESS:[/green] Created glossary term '{name}'")
|
|
826
869
|
console.print(json.dumps(result, indent=2))
|
|
827
870
|
|
|
828
871
|
except Exception as e:
|
|
@@ -831,9 +874,20 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
|
|
|
831
874
|
|
|
832
875
|
@term.command(name="list")
|
|
833
876
|
@click.option("--domain-id", required=True, help="Governance domain ID to list terms from")
|
|
834
|
-
@click.option(
|
|
835
|
-
|
|
836
|
-
"""
|
|
877
|
+
@click.option(
|
|
878
|
+
"--output",
|
|
879
|
+
type=click.Choice(["table", "json", "jsonc"]),
|
|
880
|
+
default="table",
|
|
881
|
+
help="Output format: table (default, formatted), json (plain, parseable), jsonc (colored JSON)"
|
|
882
|
+
)
|
|
883
|
+
def list_terms(domain_id, output):
|
|
884
|
+
"""List all Unified Catalog terms in a governance domain.
|
|
885
|
+
|
|
886
|
+
Output formats:
|
|
887
|
+
- table: Formatted table output with Rich (default)
|
|
888
|
+
- json: Plain JSON for scripting (use with PowerShell ConvertFrom-Json)
|
|
889
|
+
- jsonc: Colored JSON with syntax highlighting for viewing
|
|
890
|
+
"""
|
|
837
891
|
try:
|
|
838
892
|
client = UnifiedCatalogClient()
|
|
839
893
|
args = {"--governance-domain-id": [domain_id]}
|
|
@@ -858,8 +912,13 @@ def list_terms(domain_id, output_json):
|
|
|
858
912
|
console.print("[yellow]No terms found.[/yellow]")
|
|
859
913
|
return
|
|
860
914
|
|
|
861
|
-
#
|
|
862
|
-
if
|
|
915
|
+
# Handle output format
|
|
916
|
+
if output == "json":
|
|
917
|
+
# Plain JSON for scripting (PowerShell compatible)
|
|
918
|
+
print(json.dumps(all_terms, indent=2))
|
|
919
|
+
return
|
|
920
|
+
elif output == "jsonc":
|
|
921
|
+
# Colored JSON for viewing
|
|
863
922
|
_format_json_output(all_terms)
|
|
864
923
|
return
|
|
865
924
|
|
|
@@ -967,12 +1026,624 @@ def delete(term_id, force):
|
|
|
967
1026
|
gclient = Glossary()
|
|
968
1027
|
result = gclient.glossaryDeleteTerm({"--termGuid": term_id})
|
|
969
1028
|
|
|
970
|
-
console.print(f"[green]
|
|
1029
|
+
console.print(f"[green] SUCCESS:[/green] Deleted term with ID: {term_id}")
|
|
1030
|
+
|
|
1031
|
+
except Exception as e:
|
|
1032
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
@term.command()
|
|
1036
|
+
@click.option("--term-id", required=True, help="ID of the glossary term to update")
|
|
1037
|
+
@click.option("--name", required=False, help="Name of the glossary term")
|
|
1038
|
+
@click.option("--description", required=False, help="Rich text description of the term")
|
|
1039
|
+
@click.option("--domain-id", required=False, help="Governance domain ID")
|
|
1040
|
+
@click.option(
|
|
1041
|
+
"--status",
|
|
1042
|
+
required=False,
|
|
1043
|
+
type=click.Choice(["Draft", "Published", "Archived"]),
|
|
1044
|
+
help="Status of the term",
|
|
1045
|
+
)
|
|
1046
|
+
@click.option(
|
|
1047
|
+
"--acronym",
|
|
1048
|
+
required=False,
|
|
1049
|
+
help="Acronyms for the term (can be specified multiple times, replaces existing)",
|
|
1050
|
+
multiple=True,
|
|
1051
|
+
)
|
|
1052
|
+
@click.option(
|
|
1053
|
+
"--owner-id",
|
|
1054
|
+
required=False,
|
|
1055
|
+
help="Owner Entra ID (can be specified multiple times, replaces existing)",
|
|
1056
|
+
multiple=True,
|
|
1057
|
+
)
|
|
1058
|
+
@click.option("--resource-name", required=False, help="Resource name for additional reading (can be specified multiple times, replaces existing)", multiple=True)
|
|
1059
|
+
@click.option("--resource-url", required=False, help="Resource URL for additional reading (can be specified multiple times, replaces existing)", multiple=True)
|
|
1060
|
+
@click.option("--add-acronym", required=False, help="Add acronym to existing ones (can be specified multiple times)", multiple=True)
|
|
1061
|
+
@click.option("--add-owner-id", required=False, help="Add owner to existing ones (can be specified multiple times)", multiple=True)
|
|
1062
|
+
def update(term_id, name, description, domain_id, status, acronym, owner_id, resource_name, resource_url, add_acronym, add_owner_id):
|
|
1063
|
+
"""Update an existing Unified Catalog term."""
|
|
1064
|
+
try:
|
|
1065
|
+
client = UnifiedCatalogClient()
|
|
1066
|
+
|
|
1067
|
+
# Build args dictionary - only include provided values
|
|
1068
|
+
args = {"--term-id": [term_id]}
|
|
1069
|
+
|
|
1070
|
+
if name:
|
|
1071
|
+
args["--name"] = [name]
|
|
1072
|
+
if description is not None: # Allow empty string
|
|
1073
|
+
args["--description"] = [description]
|
|
1074
|
+
if domain_id:
|
|
1075
|
+
args["--governance-domain-id"] = [domain_id]
|
|
1076
|
+
if status:
|
|
1077
|
+
args["--status"] = [status]
|
|
1078
|
+
|
|
1079
|
+
# Handle acronyms - either replace or add
|
|
1080
|
+
if acronym:
|
|
1081
|
+
args["--acronym"] = list(acronym)
|
|
1082
|
+
elif add_acronym:
|
|
1083
|
+
args["--add-acronym"] = list(add_acronym)
|
|
1084
|
+
|
|
1085
|
+
# Handle owners - either replace or add
|
|
1086
|
+
if owner_id:
|
|
1087
|
+
args["--owner-id"] = list(owner_id)
|
|
1088
|
+
elif add_owner_id:
|
|
1089
|
+
args["--add-owner-id"] = list(add_owner_id)
|
|
1090
|
+
|
|
1091
|
+
# Handle resources
|
|
1092
|
+
if resource_name:
|
|
1093
|
+
args["--resource-name"] = list(resource_name)
|
|
1094
|
+
if resource_url:
|
|
1095
|
+
args["--resource-url"] = list(resource_url)
|
|
1096
|
+
|
|
1097
|
+
result = client.update_term(args)
|
|
1098
|
+
|
|
1099
|
+
if not result:
|
|
1100
|
+
console.print("[red]ERROR:[/red] No response received")
|
|
1101
|
+
return
|
|
1102
|
+
if isinstance(result, dict) and "error" in result:
|
|
1103
|
+
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1104
|
+
return
|
|
1105
|
+
|
|
1106
|
+
console.print(f"[green] SUCCESS:[/green] Updated glossary term '{term_id}'")
|
|
1107
|
+
console.print(json.dumps(result, indent=2))
|
|
1108
|
+
|
|
1109
|
+
except Exception as e:
|
|
1110
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
@term.command(name="import-csv")
|
|
1114
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="Path to CSV file with terms")
|
|
1115
|
+
@click.option("--domain-id", required=True, help="Governance domain ID for all terms")
|
|
1116
|
+
@click.option("--dry-run", is_flag=True, help="Preview terms without creating them")
|
|
1117
|
+
def import_terms_from_csv(csv_file, domain_id, dry_run):
|
|
1118
|
+
"""Bulk import glossary terms from a CSV file.
|
|
1119
|
+
|
|
1120
|
+
CSV Format:
|
|
1121
|
+
name,description,status,acronyms,owner_ids,resource_name,resource_url
|
|
1122
|
+
|
|
1123
|
+
- name: Required term name
|
|
1124
|
+
- description: Optional description
|
|
1125
|
+
- status: Draft, Published, or Archived (default: Draft)
|
|
1126
|
+
- acronyms: Comma-separated list (e.g., "API,REST")
|
|
1127
|
+
- owner_ids: Comma-separated list of Entra Object IDs
|
|
1128
|
+
- resource_name: Name of related resource
|
|
1129
|
+
- resource_url: URL of related resource
|
|
1130
|
+
|
|
1131
|
+
Multiple resources can be specified by separating with semicolons.
|
|
1132
|
+
"""
|
|
1133
|
+
try:
|
|
1134
|
+
client = UnifiedCatalogClient()
|
|
1135
|
+
|
|
1136
|
+
# Read and parse CSV
|
|
1137
|
+
terms = []
|
|
1138
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
1139
|
+
reader = csv.DictReader(f)
|
|
1140
|
+
for row in reader:
|
|
1141
|
+
term = {
|
|
1142
|
+
"name": row.get("name", "").strip(),
|
|
1143
|
+
"description": row.get("description", "").strip(),
|
|
1144
|
+
"status": row.get("status", "Draft").strip(),
|
|
1145
|
+
"domain_id": domain_id,
|
|
1146
|
+
"acronyms": [],
|
|
1147
|
+
"owner_ids": [],
|
|
1148
|
+
"resources": []
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
# Parse acronyms
|
|
1152
|
+
if row.get("acronyms"):
|
|
1153
|
+
term["acronyms"] = [a.strip() for a in row["acronyms"].split(",") if a.strip()]
|
|
1154
|
+
|
|
1155
|
+
# Parse owner IDs
|
|
1156
|
+
if row.get("owner_ids"):
|
|
1157
|
+
term["owner_ids"] = [o.strip() for o in row["owner_ids"].split(",") if o.strip()]
|
|
1158
|
+
|
|
1159
|
+
# Parse resources
|
|
1160
|
+
resource_names = row.get("resource_name", "").strip()
|
|
1161
|
+
resource_urls = row.get("resource_url", "").strip()
|
|
1162
|
+
|
|
1163
|
+
if resource_names and resource_urls:
|
|
1164
|
+
names = [n.strip() for n in resource_names.split(";") if n.strip()]
|
|
1165
|
+
urls = [u.strip() for u in resource_urls.split(";") if u.strip()]
|
|
1166
|
+
term["resources"] = [{"name": n, "url": u} for n, u in zip(names, urls)]
|
|
1167
|
+
|
|
1168
|
+
if term["name"]: # Only add if name is present
|
|
1169
|
+
terms.append(term)
|
|
1170
|
+
|
|
1171
|
+
if not terms:
|
|
1172
|
+
console.print("[yellow]No valid terms found in CSV file.[/yellow]")
|
|
1173
|
+
return
|
|
1174
|
+
|
|
1175
|
+
console.print(f"[cyan]Found {len(terms)} term(s) in CSV file[/cyan]")
|
|
1176
|
+
|
|
1177
|
+
if dry_run:
|
|
1178
|
+
console.print("\n[yellow]DRY RUN - Preview of terms to be created:[/yellow]\n")
|
|
1179
|
+
table = Table(title="Terms to Import")
|
|
1180
|
+
table.add_column("#", style="dim", width=4)
|
|
1181
|
+
table.add_column("Name", style="cyan")
|
|
1182
|
+
table.add_column("Status", style="yellow")
|
|
1183
|
+
table.add_column("Acronyms", style="magenta")
|
|
1184
|
+
table.add_column("Owners", style="green")
|
|
1185
|
+
|
|
1186
|
+
for i, term in enumerate(terms, 1):
|
|
1187
|
+
acronyms = ", ".join(term.get("acronyms", []))
|
|
1188
|
+
owners = ", ".join(term.get("owner_ids", []))
|
|
1189
|
+
table.add_row(
|
|
1190
|
+
str(i),
|
|
1191
|
+
term["name"],
|
|
1192
|
+
term["status"],
|
|
1193
|
+
acronyms or "-",
|
|
1194
|
+
owners or "-"
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
console.print(table)
|
|
1198
|
+
console.print(f"\n[dim]Domain ID: {domain_id}[/dim]")
|
|
1199
|
+
return
|
|
1200
|
+
|
|
1201
|
+
# Import terms (one by one using single POST)
|
|
1202
|
+
success_count = 0
|
|
1203
|
+
failed_count = 0
|
|
1204
|
+
failed_terms = []
|
|
1205
|
+
|
|
1206
|
+
with console.status("[bold green]Importing terms...") as status:
|
|
1207
|
+
for i, term in enumerate(terms, 1):
|
|
1208
|
+
status.update(f"[bold green]Creating term {i}/{len(terms)}: {term['name']}")
|
|
1209
|
+
|
|
1210
|
+
try:
|
|
1211
|
+
# Create individual term
|
|
1212
|
+
args = {
|
|
1213
|
+
"--name": [term["name"]],
|
|
1214
|
+
"--description": [term.get("description", "")],
|
|
1215
|
+
"--governance-domain-id": [term["domain_id"]],
|
|
1216
|
+
"--status": [term.get("status", "Draft")],
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if term.get("acronyms"):
|
|
1220
|
+
args["--acronym"] = term["acronyms"]
|
|
1221
|
+
|
|
1222
|
+
if term.get("owner_ids"):
|
|
1223
|
+
args["--owner-id"] = term["owner_ids"]
|
|
1224
|
+
|
|
1225
|
+
if term.get("resources"):
|
|
1226
|
+
args["--resource-name"] = [r["name"] for r in term["resources"]]
|
|
1227
|
+
args["--resource-url"] = [r["url"] for r in term["resources"]]
|
|
1228
|
+
|
|
1229
|
+
result = client.create_term(args)
|
|
1230
|
+
|
|
1231
|
+
# Check if result contains an ID (indicates successful creation)
|
|
1232
|
+
if result and isinstance(result, dict) and result.get("id"):
|
|
1233
|
+
success_count += 1
|
|
1234
|
+
term_id = result.get("id")
|
|
1235
|
+
console.print(f"[green]Created: {term['name']} (ID: {term_id})[/green]")
|
|
1236
|
+
elif result and not (isinstance(result, dict) and "error" in result):
|
|
1237
|
+
# Got a response but no ID - might be an issue
|
|
1238
|
+
console.print(f"[yellow]WARNING: Response received for {term['name']} but no ID returned[/yellow]")
|
|
1239
|
+
console.print(f"[dim]Response: {json.dumps(result, indent=2)[:200]}...[/dim]")
|
|
1240
|
+
failed_count += 1
|
|
1241
|
+
failed_terms.append({"name": term["name"], "error": "No ID in response"})
|
|
1242
|
+
else:
|
|
1243
|
+
failed_count += 1
|
|
1244
|
+
error_msg = result.get("error", "Unknown error") if isinstance(result, dict) else "No response"
|
|
1245
|
+
failed_terms.append({"name": term["name"], "error": error_msg})
|
|
1246
|
+
console.print(f"[red]FAILED: {term['name']} - {error_msg}[/red]")
|
|
1247
|
+
|
|
1248
|
+
except Exception as e:
|
|
1249
|
+
failed_count += 1
|
|
1250
|
+
failed_terms.append({"name": term["name"], "error": str(e)})
|
|
1251
|
+
console.print(f"[red]FAILED: {term['name']} - {str(e)}[/red]")
|
|
1252
|
+
|
|
1253
|
+
# Summary
|
|
1254
|
+
console.print("\n" + "="*60)
|
|
1255
|
+
console.print(f"[cyan]Import Summary:[/cyan]")
|
|
1256
|
+
console.print(f" Total terms: {len(terms)}")
|
|
1257
|
+
console.print(f" [green]Successfully created: {success_count}[/green]")
|
|
1258
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1259
|
+
|
|
1260
|
+
if failed_terms:
|
|
1261
|
+
console.print("\n[red]Failed Terms:[/red]")
|
|
1262
|
+
for ft in failed_terms:
|
|
1263
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1264
|
+
|
|
1265
|
+
except Exception as e:
|
|
1266
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
@term.command(name="import-json")
|
|
1270
|
+
@click.option("--json-file", required=True, type=click.Path(exists=True), help="Path to JSON file with terms")
|
|
1271
|
+
@click.option("--dry-run", is_flag=True, help="Preview terms without creating them")
|
|
1272
|
+
def import_terms_from_json(json_file, dry_run):
|
|
1273
|
+
"""Bulk import glossary terms from a JSON file.
|
|
1274
|
+
|
|
1275
|
+
JSON Format:
|
|
1276
|
+
[
|
|
1277
|
+
{
|
|
1278
|
+
"name": "Term Name",
|
|
1279
|
+
"description": "Description",
|
|
1280
|
+
"domain_id": "domain-guid",
|
|
1281
|
+
"status": "Draft",
|
|
1282
|
+
"acronyms": ["API", "REST"],
|
|
1283
|
+
"owner_ids": ["owner-guid-1"],
|
|
1284
|
+
"resources": [
|
|
1285
|
+
{"name": "Resource Name", "url": "https://example.com"}
|
|
1286
|
+
]
|
|
1287
|
+
}
|
|
1288
|
+
]
|
|
1289
|
+
|
|
1290
|
+
Each term must include domain_id.
|
|
1291
|
+
"""
|
|
1292
|
+
try:
|
|
1293
|
+
client = UnifiedCatalogClient()
|
|
1294
|
+
|
|
1295
|
+
# Read and parse JSON
|
|
1296
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
1297
|
+
terms = json.load(f)
|
|
1298
|
+
|
|
1299
|
+
if not isinstance(terms, list):
|
|
1300
|
+
console.print("[red]ERROR:[/red] JSON file must contain an array of terms")
|
|
1301
|
+
return
|
|
1302
|
+
|
|
1303
|
+
if not terms:
|
|
1304
|
+
console.print("[yellow]No terms found in JSON file.[/yellow]")
|
|
1305
|
+
return
|
|
1306
|
+
|
|
1307
|
+
console.print(f"[cyan]Found {len(terms)} term(s) in JSON file[/cyan]")
|
|
1308
|
+
|
|
1309
|
+
if dry_run:
|
|
1310
|
+
console.print("\n[yellow]DRY RUN - Preview of terms to be created:[/yellow]\n")
|
|
1311
|
+
_format_json_output(terms)
|
|
1312
|
+
return
|
|
1313
|
+
|
|
1314
|
+
# Import terms
|
|
1315
|
+
success_count = 0
|
|
1316
|
+
failed_count = 0
|
|
1317
|
+
failed_terms = []
|
|
1318
|
+
|
|
1319
|
+
with console.status("[bold green]Importing terms...") as status:
|
|
1320
|
+
for i, term in enumerate(terms, 1):
|
|
1321
|
+
term_name = term.get("name", f"Term {i}")
|
|
1322
|
+
status.update(f"[bold green]Creating term {i}/{len(terms)}: {term_name}")
|
|
1323
|
+
|
|
1324
|
+
try:
|
|
1325
|
+
args = {
|
|
1326
|
+
"--name": [term.get("name", "")],
|
|
1327
|
+
"--description": [term.get("description", "")],
|
|
1328
|
+
"--governance-domain-id": [term.get("domain_id", "")],
|
|
1329
|
+
"--status": [term.get("status", "Draft")],
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if term.get("acronyms"):
|
|
1333
|
+
args["--acronym"] = term["acronyms"]
|
|
1334
|
+
|
|
1335
|
+
if term.get("owner_ids"):
|
|
1336
|
+
args["--owner-id"] = term["owner_ids"]
|
|
1337
|
+
|
|
1338
|
+
if term.get("resources"):
|
|
1339
|
+
args["--resource-name"] = [r.get("name", "") for r in term["resources"]]
|
|
1340
|
+
args["--resource-url"] = [r.get("url", "") for r in term["resources"]]
|
|
1341
|
+
|
|
1342
|
+
result = client.create_term(args)
|
|
1343
|
+
|
|
1344
|
+
# Check if result contains an ID (indicates successful creation)
|
|
1345
|
+
if result and isinstance(result, dict) and result.get("id"):
|
|
1346
|
+
success_count += 1
|
|
1347
|
+
term_id = result.get("id")
|
|
1348
|
+
console.print(f"[green]Created: {term_name} (ID: {term_id})[/green]")
|
|
1349
|
+
elif result and not (isinstance(result, dict) and "error" in result):
|
|
1350
|
+
# Got a response but no ID - might be an issue
|
|
1351
|
+
console.print(f"[yellow]WARNING: Response received for {term_name} but no ID returned[/yellow]")
|
|
1352
|
+
console.print(f"[dim]Response: {json.dumps(result, indent=2)[:200]}...[/dim]")
|
|
1353
|
+
failed_count += 1
|
|
1354
|
+
failed_terms.append({"name": term_name, "error": "No ID in response"})
|
|
1355
|
+
else:
|
|
1356
|
+
failed_count += 1
|
|
1357
|
+
error_msg = result.get("error", "Unknown error") if isinstance(result, dict) else "No response"
|
|
1358
|
+
failed_terms.append({"name": term_name, "error": error_msg})
|
|
1359
|
+
console.print(f"[red]FAILED: {term_name} - {error_msg}[/red]")
|
|
1360
|
+
|
|
1361
|
+
except Exception as e:
|
|
1362
|
+
failed_count += 1
|
|
1363
|
+
failed_terms.append({"name": term_name, "error": str(e)})
|
|
1364
|
+
console.print(f"[red]FAILED: {term_name} - {str(e)}[/red]")
|
|
1365
|
+
|
|
1366
|
+
# Summary
|
|
1367
|
+
console.print("\n" + "="*60)
|
|
1368
|
+
console.print(f"[cyan]Import Summary:[/cyan]")
|
|
1369
|
+
console.print(f" Total terms: {len(terms)}")
|
|
1370
|
+
console.print(f" [green]Successfully created: {success_count}[/green]")
|
|
1371
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1372
|
+
|
|
1373
|
+
if failed_terms:
|
|
1374
|
+
console.print("\n[red]Failed Terms:[/red]")
|
|
1375
|
+
for ft in failed_terms:
|
|
1376
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1377
|
+
|
|
1378
|
+
except Exception as e:
|
|
1379
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
@term.command(name="update-csv")
|
|
1383
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="Path to CSV file with term updates")
|
|
1384
|
+
@click.option("--dry-run", is_flag=True, help="Preview updates without applying them")
|
|
1385
|
+
def update_terms_from_csv(csv_file, dry_run):
|
|
1386
|
+
"""Bulk update glossary terms from a CSV file.
|
|
1387
|
+
|
|
1388
|
+
CSV Format:
|
|
1389
|
+
term_id,name,description,status,acronyms,owner_ids,add_acronyms,add_owner_ids
|
|
1390
|
+
|
|
1391
|
+
Required:
|
|
1392
|
+
- term_id: The ID of the term to update
|
|
1393
|
+
|
|
1394
|
+
Optional (leave empty to skip update):
|
|
1395
|
+
- name: New term name (replaces existing)
|
|
1396
|
+
- description: New description (replaces existing)
|
|
1397
|
+
- status: New status (Draft, Published, Archived)
|
|
1398
|
+
- acronyms: New acronyms separated by semicolons (replaces all existing)
|
|
1399
|
+
- owner_ids: New owner IDs separated by semicolons (replaces all existing)
|
|
1400
|
+
- add_acronyms: Acronyms to add separated by semicolons (preserves existing)
|
|
1401
|
+
- add_owner_ids: Owner IDs to add separated by semicolons (preserves existing)
|
|
1402
|
+
|
|
1403
|
+
Example CSV:
|
|
1404
|
+
term_id,name,description,status,add_acronyms,add_owner_ids
|
|
1405
|
+
abc-123,,Updated description,Published,API;REST,user1@company.com
|
|
1406
|
+
def-456,New Name,,,SQL,
|
|
1407
|
+
"""
|
|
1408
|
+
import csv
|
|
1409
|
+
|
|
1410
|
+
try:
|
|
1411
|
+
# Read CSV file
|
|
1412
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
1413
|
+
reader = csv.DictReader(f)
|
|
1414
|
+
updates = list(reader)
|
|
1415
|
+
|
|
1416
|
+
if not updates:
|
|
1417
|
+
console.print("[yellow]No updates found in CSV file.[/yellow]")
|
|
1418
|
+
return
|
|
1419
|
+
|
|
1420
|
+
console.print(f"Found {len(updates)} term(s) to update in CSV file")
|
|
1421
|
+
|
|
1422
|
+
# Dry run preview
|
|
1423
|
+
if dry_run:
|
|
1424
|
+
console.print("\n[cyan]DRY RUN - Preview of updates to be applied:[/cyan]\n")
|
|
1425
|
+
|
|
1426
|
+
table = Table(title="Terms to Update")
|
|
1427
|
+
table.add_column("#", style="cyan")
|
|
1428
|
+
table.add_column("Term ID", style="yellow")
|
|
1429
|
+
table.add_column("Updates", style="white")
|
|
1430
|
+
|
|
1431
|
+
for idx, update in enumerate(updates, 1):
|
|
1432
|
+
term_id = update.get('term_id', '').strip()
|
|
1433
|
+
if not term_id:
|
|
1434
|
+
continue
|
|
1435
|
+
|
|
1436
|
+
changes = []
|
|
1437
|
+
if update.get('name', '').strip():
|
|
1438
|
+
changes.append(f"name: {update['name']}")
|
|
1439
|
+
if update.get('description', '').strip():
|
|
1440
|
+
changes.append(f"desc: {update['description'][:50]}...")
|
|
1441
|
+
if update.get('status', '').strip():
|
|
1442
|
+
changes.append(f"status: {update['status']}")
|
|
1443
|
+
if update.get('acronyms', '').strip():
|
|
1444
|
+
changes.append(f"acronyms: {update['acronyms']}")
|
|
1445
|
+
if update.get('add_acronyms', '').strip():
|
|
1446
|
+
changes.append(f"add acronyms: {update['add_acronyms']}")
|
|
1447
|
+
if update.get('owner_ids', '').strip():
|
|
1448
|
+
changes.append(f"owners: {update['owner_ids']}")
|
|
1449
|
+
if update.get('add_owner_ids', '').strip():
|
|
1450
|
+
changes.append(f"add owners: {update['add_owner_ids']}")
|
|
1451
|
+
|
|
1452
|
+
table.add_row(str(idx), term_id[:36], ", ".join(changes) if changes else "No changes")
|
|
1453
|
+
|
|
1454
|
+
console.print(table)
|
|
1455
|
+
console.print(f"\n[yellow]Total terms to update: {len(updates)}[/yellow]")
|
|
1456
|
+
return
|
|
1457
|
+
|
|
1458
|
+
# Apply updates
|
|
1459
|
+
console.print("\n[cyan]Updating terms...[/cyan]\n")
|
|
1460
|
+
|
|
1461
|
+
client = UnifiedCatalogClient()
|
|
1462
|
+
success_count = 0
|
|
1463
|
+
failed_count = 0
|
|
1464
|
+
failed_terms = []
|
|
1465
|
+
|
|
1466
|
+
for idx, update in enumerate(updates, 1):
|
|
1467
|
+
term_id = update.get('term_id', '').strip()
|
|
1468
|
+
if not term_id:
|
|
1469
|
+
console.print(f"[yellow]Skipping row {idx}: Missing term_id[/yellow]")
|
|
1470
|
+
continue
|
|
1471
|
+
|
|
1472
|
+
# Build update arguments
|
|
1473
|
+
args = {"--term-id": [term_id]}
|
|
1474
|
+
|
|
1475
|
+
# Add replace operations
|
|
1476
|
+
if update.get('name', '').strip():
|
|
1477
|
+
args['--name'] = [update['name'].strip()]
|
|
1478
|
+
if update.get('description', '').strip():
|
|
1479
|
+
args['--description'] = [update['description'].strip()]
|
|
1480
|
+
if update.get('status', '').strip():
|
|
1481
|
+
args['--status'] = [update['status'].strip()]
|
|
1482
|
+
if update.get('acronyms', '').strip():
|
|
1483
|
+
args['--acronym'] = [a.strip() for a in update['acronyms'].split(';') if a.strip()]
|
|
1484
|
+
if update.get('owner_ids', '').strip():
|
|
1485
|
+
args['--owner-id'] = [o.strip() for o in update['owner_ids'].split(';') if o.strip()]
|
|
1486
|
+
|
|
1487
|
+
# Add "add" operations
|
|
1488
|
+
if update.get('add_acronyms', '').strip():
|
|
1489
|
+
args['--add-acronym'] = [a.strip() for a in update['add_acronyms'].split(';') if a.strip()]
|
|
1490
|
+
if update.get('add_owner_ids', '').strip():
|
|
1491
|
+
args['--add-owner-id'] = [o.strip() for o in update['add_owner_ids'].split(';') if o.strip()]
|
|
1492
|
+
|
|
1493
|
+
# Display progress
|
|
1494
|
+
display_name = update.get('name', term_id[:36])
|
|
1495
|
+
console.status(f"[{idx}/{len(updates)}] Updating: {display_name}...")
|
|
1496
|
+
|
|
1497
|
+
try:
|
|
1498
|
+
result = client.update_term(args)
|
|
1499
|
+
console.print(f"[green]SUCCESS:[/green] Updated term {idx}/{len(updates)}")
|
|
1500
|
+
success_count += 1
|
|
1501
|
+
except Exception as e:
|
|
1502
|
+
error_msg = str(e)
|
|
1503
|
+
console.print(f"[red]FAILED:[/red] {display_name}: {error_msg}")
|
|
1504
|
+
failed_terms.append({'term_id': term_id, 'name': display_name, 'error': error_msg})
|
|
1505
|
+
failed_count += 1
|
|
1506
|
+
|
|
1507
|
+
# Rate limiting
|
|
1508
|
+
time.sleep(0.2)
|
|
1509
|
+
|
|
1510
|
+
# Summary
|
|
1511
|
+
console.print("\n" + "="*60)
|
|
1512
|
+
console.print(f"[cyan]Update Summary:[/cyan]")
|
|
1513
|
+
console.print(f" Total terms: {len(updates)}")
|
|
1514
|
+
console.print(f" [green]Successfully updated: {success_count}[/green]")
|
|
1515
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1516
|
+
|
|
1517
|
+
if failed_terms:
|
|
1518
|
+
console.print("\n[red]Failed Updates:[/red]")
|
|
1519
|
+
for ft in failed_terms:
|
|
1520
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1521
|
+
|
|
1522
|
+
except Exception as e:
|
|
1523
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
@term.command(name="update-json")
|
|
1527
|
+
@click.option("--json-file", required=True, type=click.Path(exists=True), help="Path to JSON file with term updates")
|
|
1528
|
+
@click.option("--dry-run", is_flag=True, help="Preview updates without applying them")
|
|
1529
|
+
def update_terms_from_json(json_file, dry_run):
|
|
1530
|
+
"""Bulk update glossary terms from a JSON file.
|
|
1531
|
+
|
|
1532
|
+
JSON Format:
|
|
1533
|
+
{
|
|
1534
|
+
"updates": [
|
|
1535
|
+
{
|
|
1536
|
+
"term_id": "term-guid",
|
|
1537
|
+
"name": "New Name", // Optional: Replace name
|
|
1538
|
+
"description": "New description", // Optional: Replace description
|
|
1539
|
+
"status": "Published", // Optional: Change status
|
|
1540
|
+
"acronyms": ["API", "REST"], // Optional: Replace all acronyms
|
|
1541
|
+
"owner_ids": ["user@company.com"], // Optional: Replace all owners
|
|
1542
|
+
"add_acronyms": ["SQL"], // Optional: Add acronyms (preserves existing)
|
|
1543
|
+
"add_owner_ids": ["user2@company.com"] // Optional: Add owners (preserves existing)
|
|
1544
|
+
}
|
|
1545
|
+
]
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
Note: Leave fields empty or omit them to skip that update.
|
|
1549
|
+
"""
|
|
1550
|
+
import json
|
|
1551
|
+
|
|
1552
|
+
try:
|
|
1553
|
+
# Read JSON file
|
|
1554
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
1555
|
+
data = json.load(f)
|
|
1556
|
+
|
|
1557
|
+
updates = data.get('updates', [])
|
|
1558
|
+
|
|
1559
|
+
if not updates:
|
|
1560
|
+
console.print("[yellow]No updates found in JSON file.[/yellow]")
|
|
1561
|
+
return
|
|
1562
|
+
|
|
1563
|
+
console.print(f"Found {len(updates)} term(s) to update in JSON file")
|
|
1564
|
+
|
|
1565
|
+
# Dry run preview
|
|
1566
|
+
if dry_run:
|
|
1567
|
+
console.print("\n[cyan]DRY RUN - Preview of updates to be applied:[/cyan]\n")
|
|
1568
|
+
|
|
1569
|
+
# Display updates in colored JSON
|
|
1570
|
+
from rich.syntax import Syntax
|
|
1571
|
+
json_str = json.dumps(data, indent=2)
|
|
1572
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=True)
|
|
1573
|
+
console.print(syntax)
|
|
1574
|
+
|
|
1575
|
+
console.print(f"\n[yellow]Total terms to update: {len(updates)}[/yellow]")
|
|
1576
|
+
return
|
|
1577
|
+
|
|
1578
|
+
# Apply updates
|
|
1579
|
+
console.print("\n[cyan]Updating terms...[/cyan]\n")
|
|
1580
|
+
|
|
1581
|
+
client = UnifiedCatalogClient()
|
|
1582
|
+
success_count = 0
|
|
1583
|
+
failed_count = 0
|
|
1584
|
+
failed_terms = []
|
|
1585
|
+
|
|
1586
|
+
for idx, update in enumerate(updates, 1):
|
|
1587
|
+
term_id = update.get('term_id', '').strip() if isinstance(update.get('term_id'), str) else ''
|
|
1588
|
+
if not term_id:
|
|
1589
|
+
console.print(f"[yellow]Skipping update {idx}: Missing term_id[/yellow]")
|
|
1590
|
+
continue
|
|
1591
|
+
|
|
1592
|
+
# Build update arguments
|
|
1593
|
+
args = {"--term-id": [term_id]}
|
|
1594
|
+
|
|
1595
|
+
# Add replace operations
|
|
1596
|
+
if update.get('name'):
|
|
1597
|
+
args['--name'] = [update['name']]
|
|
1598
|
+
if update.get('description'):
|
|
1599
|
+
args['--description'] = [update['description']]
|
|
1600
|
+
if update.get('status'):
|
|
1601
|
+
args['--status'] = [update['status']]
|
|
1602
|
+
if update.get('acronyms'):
|
|
1603
|
+
args['--acronym'] = update['acronyms'] if isinstance(update['acronyms'], list) else [update['acronyms']]
|
|
1604
|
+
if update.get('owner_ids'):
|
|
1605
|
+
args['--owner-id'] = update['owner_ids'] if isinstance(update['owner_ids'], list) else [update['owner_ids']]
|
|
1606
|
+
|
|
1607
|
+
# Add "add" operations
|
|
1608
|
+
if update.get('add_acronyms'):
|
|
1609
|
+
args['--add-acronym'] = update['add_acronyms'] if isinstance(update['add_acronyms'], list) else [update['add_acronyms']]
|
|
1610
|
+
if update.get('add_owner_ids'):
|
|
1611
|
+
args['--add-owner-id'] = update['add_owner_ids'] if isinstance(update['add_owner_ids'], list) else [update['add_owner_ids']]
|
|
1612
|
+
|
|
1613
|
+
# Display progress
|
|
1614
|
+
display_name = update.get('name', term_id[:36])
|
|
1615
|
+
console.status(f"[{idx}/{len(updates)}] Updating: {display_name}...")
|
|
1616
|
+
|
|
1617
|
+
try:
|
|
1618
|
+
result = client.update_term(args)
|
|
1619
|
+
console.print(f"[green]SUCCESS:[/green] Updated term {idx}/{len(updates)}")
|
|
1620
|
+
success_count += 1
|
|
1621
|
+
except Exception as e:
|
|
1622
|
+
error_msg = str(e)
|
|
1623
|
+
console.print(f"[red]FAILED:[/red] {display_name}: {error_msg}")
|
|
1624
|
+
failed_terms.append({'term_id': term_id, 'name': display_name, 'error': error_msg})
|
|
1625
|
+
failed_count += 1
|
|
1626
|
+
|
|
1627
|
+
# Rate limiting
|
|
1628
|
+
time.sleep(0.2)
|
|
1629
|
+
|
|
1630
|
+
# Summary
|
|
1631
|
+
console.print("\n" + "="*60)
|
|
1632
|
+
console.print(f"[cyan]Update Summary:[/cyan]")
|
|
1633
|
+
console.print(f" Total terms: {len(updates)}")
|
|
1634
|
+
console.print(f" [green]Successfully updated: {success_count}[/green]")
|
|
1635
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1636
|
+
|
|
1637
|
+
if failed_terms:
|
|
1638
|
+
console.print("\n[red]Failed Updates:[/red]")
|
|
1639
|
+
for ft in failed_terms:
|
|
1640
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
971
1641
|
|
|
972
1642
|
except Exception as e:
|
|
973
1643
|
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
974
1644
|
|
|
975
1645
|
|
|
1646
|
+
|
|
976
1647
|
# ========================================
|
|
977
1648
|
# OBJECTIVES AND KEY RESULTS (OKRs)
|
|
978
1649
|
# ========================================
|
|
@@ -1028,7 +1699,7 @@ def create(definition, domain_id, status, owner_id, target_date):
|
|
|
1028
1699
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1029
1700
|
return
|
|
1030
1701
|
|
|
1031
|
-
console.print(f"[green]
|
|
1702
|
+
console.print(f"[green] SUCCESS:[/green] Created objective")
|
|
1032
1703
|
console.print(json.dumps(result, indent=2))
|
|
1033
1704
|
|
|
1034
1705
|
except Exception as e:
|
|
@@ -1171,7 +1842,7 @@ def create(name, description, domain_id, data_type, status, owner_id):
|
|
|
1171
1842
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1172
1843
|
return
|
|
1173
1844
|
|
|
1174
|
-
console.print(f"[green]
|
|
1845
|
+
console.print(f"[green] SUCCESS:[/green] Created critical data element '{name}'")
|
|
1175
1846
|
console.print(json.dumps(result, indent=2))
|
|
1176
1847
|
|
|
1177
1848
|
except Exception as e:
|
|
@@ -1258,7 +1929,7 @@ def show(cde_id):
|
|
|
1258
1929
|
|
|
1259
1930
|
|
|
1260
1931
|
# ========================================
|
|
1261
|
-
# HEALTH MANAGEMENT - IMPLEMENTED!
|
|
1932
|
+
# HEALTH MANAGEMENT - IMPLEMENTED!
|
|
1262
1933
|
# ========================================
|
|
1263
1934
|
|
|
1264
1935
|
# Import and register health commands from dedicated module
|