pvw-cli 1.0.12__py3-none-any.whl → 1.2.0__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 +328 -222
- 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 +610 -29
- purviewcli/cli/workflow.py +43 -43
- purviewcli/client/_entity.py +35 -0
- purviewcli/client/_unified_catalog.py +7 -0
- {pvw_cli-1.0.12.dist-info → pvw_cli-1.2.0.dist-info}/METADATA +378 -58
- {pvw_cli-1.0.12.dist-info → pvw_cli-1.2.0.dist-info}/RECORD +19 -19
- {pvw_cli-1.0.12.dist-info → pvw_cli-1.2.0.dist-info}/WHEEL +0 -0
- {pvw_cli-1.0.12.dist-info → pvw_cli-1.2.0.dist-info}/entry_points.txt +0 -0
- {pvw_cli-1.0.12.dist-info → pvw_cli-1.2.0.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
|
|
@@ -115,7 +116,7 @@ def create(name, description, type, owner_id, status, parent_id, payload_file):
|
|
|
115
116
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
116
117
|
return
|
|
117
118
|
|
|
118
|
-
console.print(f"[green]
|
|
119
|
+
console.print(f"[green] SUCCESS:[/green] Created governance domain '{name}'")
|
|
119
120
|
console.print(json.dumps(result, indent=2))
|
|
120
121
|
|
|
121
122
|
except Exception as e:
|
|
@@ -123,9 +124,20 @@ def create(name, description, type, owner_id, status, parent_id, payload_file):
|
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
@domain.command(name="list")
|
|
126
|
-
@click.option(
|
|
127
|
-
|
|
128
|
-
"""
|
|
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
|
+
"""
|
|
129
141
|
try:
|
|
130
142
|
client = UnifiedCatalogClient()
|
|
131
143
|
args = {} # No arguments needed for list operation
|
|
@@ -147,8 +159,13 @@ def list_domains(output_json):
|
|
|
147
159
|
console.print("[yellow]No governance domains found.[/yellow]")
|
|
148
160
|
return
|
|
149
161
|
|
|
150
|
-
#
|
|
151
|
-
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
|
|
152
169
|
_format_json_output(domains)
|
|
153
170
|
return
|
|
154
171
|
|
|
@@ -274,7 +291,7 @@ def create(
|
|
|
274
291
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
275
292
|
return
|
|
276
293
|
|
|
277
|
-
console.print(f"[green]
|
|
294
|
+
console.print(f"[green] SUCCESS:[/green] Created data product '{name}'")
|
|
278
295
|
console.print(json.dumps(result, indent=2))
|
|
279
296
|
|
|
280
297
|
except Exception as e:
|
|
@@ -444,7 +461,7 @@ def update(
|
|
|
444
461
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
445
462
|
return
|
|
446
463
|
|
|
447
|
-
console.print(f"[green]
|
|
464
|
+
console.print(f"[green] SUCCESS:[/green] Updated data product '{product_id}'")
|
|
448
465
|
console.print(json.dumps(result, indent=2))
|
|
449
466
|
|
|
450
467
|
except Exception as e:
|
|
@@ -472,11 +489,11 @@ def delete(product_id, yes):
|
|
|
472
489
|
|
|
473
490
|
# DELETE operations may return empty response on success
|
|
474
491
|
if result is None or (isinstance(result, dict) and not result.get("error")):
|
|
475
|
-
console.print(f"[green]
|
|
492
|
+
console.print(f"[green] SUCCESS:[/green] Deleted data product '{product_id}'")
|
|
476
493
|
elif isinstance(result, dict) and "error" in result:
|
|
477
494
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
478
495
|
else:
|
|
479
|
-
console.print(f"[green]
|
|
496
|
+
console.print(f"[green] SUCCESS:[/green] Deleted data product '{product_id}'")
|
|
480
497
|
if result:
|
|
481
498
|
console.print(json.dumps(result, indent=2))
|
|
482
499
|
|
|
@@ -579,7 +596,7 @@ def create_glossary(name, description, domain_id):
|
|
|
579
596
|
return
|
|
580
597
|
|
|
581
598
|
guid = result.get("guid") if isinstance(result, dict) else None
|
|
582
|
-
console.print(f"[green]
|
|
599
|
+
console.print(f"[green] SUCCESS:[/green] Created glossary '{name}'")
|
|
583
600
|
if guid:
|
|
584
601
|
console.print(f"[cyan]GUID:[/cyan] {guid}")
|
|
585
602
|
console.print(f"\n[dim]Use this GUID: --glossary-guid {guid}[/dim]")
|
|
@@ -664,13 +681,13 @@ def create_glossaries_for_domains():
|
|
|
664
681
|
guid = result.get("guid") if isinstance(result, dict) else None
|
|
665
682
|
|
|
666
683
|
if guid:
|
|
667
|
-
console.print(f"[green]
|
|
684
|
+
console.print(f"[green] Created:[/green] {glossary_name} (GUID: {guid})")
|
|
668
685
|
created_count += 1
|
|
669
686
|
else:
|
|
670
|
-
console.print(f"[yellow]
|
|
687
|
+
console.print(f"[yellow] Created {glossary_name} but no GUID returned[/yellow]")
|
|
671
688
|
|
|
672
689
|
except Exception as e:
|
|
673
|
-
console.print(f"[red]
|
|
690
|
+
console.print(f"[red] Failed to create {glossary_name}:[/red] {str(e)}")
|
|
674
691
|
|
|
675
692
|
console.print(f"\n[cyan]Created {created_count} new glossaries[/cyan]")
|
|
676
693
|
console.print("[dim]Run 'pvw uc glossary list' to see all glossaries[/dim]")
|
|
@@ -756,7 +773,7 @@ def verify_glossary_links():
|
|
|
756
773
|
domain_id[:8] + "...",
|
|
757
774
|
glossary_info["name"],
|
|
758
775
|
glossary_info["guid"][:8] + "...",
|
|
759
|
-
"[green]
|
|
776
|
+
"[green] Linked[/green]"
|
|
760
777
|
)
|
|
761
778
|
linked_count += 1
|
|
762
779
|
else:
|
|
@@ -765,7 +782,7 @@ def verify_glossary_links():
|
|
|
765
782
|
domain_id[:8] + "...",
|
|
766
783
|
"[dim]No glossary[/dim]",
|
|
767
784
|
"[dim]N/A[/dim]",
|
|
768
|
-
"[yellow]
|
|
785
|
+
"[yellow] Not Linked[/yellow]"
|
|
769
786
|
)
|
|
770
787
|
unlinked_count += 1
|
|
771
788
|
|
|
@@ -796,6 +813,7 @@ def term():
|
|
|
796
813
|
@click.option("--name", required=True, help="Name of the glossary term")
|
|
797
814
|
@click.option("--description", required=False, default="", help="Rich text description of the term")
|
|
798
815
|
@click.option("--domain-id", required=True, help="Governance domain ID")
|
|
816
|
+
@click.option("--parent-id", required=False, help="Parent term ID (for hierarchical terms)")
|
|
799
817
|
@click.option(
|
|
800
818
|
"--status",
|
|
801
819
|
required=False,
|
|
@@ -817,7 +835,7 @@ def term():
|
|
|
817
835
|
)
|
|
818
836
|
@click.option("--resource-name", required=False, help="Resource name for additional reading (can be specified multiple times)", multiple=True)
|
|
819
837
|
@click.option("--resource-url", required=False, help="Resource URL for additional reading (can be specified multiple times)", multiple=True)
|
|
820
|
-
def create(name, description, domain_id, status, acronym, owner_id, resource_name, resource_url):
|
|
838
|
+
def create(name, description, domain_id, parent_id, status, acronym, owner_id, resource_name, resource_url):
|
|
821
839
|
"""Create a new Unified Catalog term (Governance Domain term)."""
|
|
822
840
|
try:
|
|
823
841
|
client = UnifiedCatalogClient()
|
|
@@ -830,6 +848,8 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
|
|
|
830
848
|
"--status": [status],
|
|
831
849
|
}
|
|
832
850
|
|
|
851
|
+
if parent_id:
|
|
852
|
+
args["--parent-id"] = [parent_id]
|
|
833
853
|
if acronym:
|
|
834
854
|
args["--acronym"] = list(acronym)
|
|
835
855
|
if owner_id:
|
|
@@ -848,7 +868,7 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
|
|
|
848
868
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
849
869
|
return
|
|
850
870
|
|
|
851
|
-
console.print(f"[green]
|
|
871
|
+
console.print(f"[green] SUCCESS:[/green] Created glossary term '{name}'")
|
|
852
872
|
console.print(json.dumps(result, indent=2))
|
|
853
873
|
|
|
854
874
|
except Exception as e:
|
|
@@ -857,9 +877,20 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
|
|
|
857
877
|
|
|
858
878
|
@term.command(name="list")
|
|
859
879
|
@click.option("--domain-id", required=True, help="Governance domain ID to list terms from")
|
|
860
|
-
@click.option(
|
|
861
|
-
|
|
862
|
-
"""
|
|
880
|
+
@click.option(
|
|
881
|
+
"--output",
|
|
882
|
+
type=click.Choice(["table", "json", "jsonc"]),
|
|
883
|
+
default="table",
|
|
884
|
+
help="Output format: table (default, formatted), json (plain, parseable), jsonc (colored JSON)"
|
|
885
|
+
)
|
|
886
|
+
def list_terms(domain_id, output):
|
|
887
|
+
"""List all Unified Catalog terms in a governance domain.
|
|
888
|
+
|
|
889
|
+
Output formats:
|
|
890
|
+
- table: Formatted table output with Rich (default)
|
|
891
|
+
- json: Plain JSON for scripting (use with PowerShell ConvertFrom-Json)
|
|
892
|
+
- jsonc: Colored JSON with syntax highlighting for viewing
|
|
893
|
+
"""
|
|
863
894
|
try:
|
|
864
895
|
client = UnifiedCatalogClient()
|
|
865
896
|
args = {"--governance-domain-id": [domain_id]}
|
|
@@ -884,8 +915,13 @@ def list_terms(domain_id, output_json):
|
|
|
884
915
|
console.print("[yellow]No terms found.[/yellow]")
|
|
885
916
|
return
|
|
886
917
|
|
|
887
|
-
#
|
|
888
|
-
if
|
|
918
|
+
# Handle output format
|
|
919
|
+
if output == "json":
|
|
920
|
+
# Plain JSON for scripting (PowerShell compatible)
|
|
921
|
+
print(json.dumps(all_terms, indent=2))
|
|
922
|
+
return
|
|
923
|
+
elif output == "jsonc":
|
|
924
|
+
# Colored JSON for viewing
|
|
889
925
|
_format_json_output(all_terms)
|
|
890
926
|
return
|
|
891
927
|
|
|
@@ -993,7 +1029,7 @@ def delete(term_id, force):
|
|
|
993
1029
|
gclient = Glossary()
|
|
994
1030
|
result = gclient.glossaryDeleteTerm({"--termGuid": term_id})
|
|
995
1031
|
|
|
996
|
-
console.print(f"[green]
|
|
1032
|
+
console.print(f"[green] SUCCESS:[/green] Deleted term with ID: {term_id}")
|
|
997
1033
|
|
|
998
1034
|
except Exception as e:
|
|
999
1035
|
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
@@ -1004,6 +1040,7 @@ def delete(term_id, force):
|
|
|
1004
1040
|
@click.option("--name", required=False, help="Name of the glossary term")
|
|
1005
1041
|
@click.option("--description", required=False, help="Rich text description of the term")
|
|
1006
1042
|
@click.option("--domain-id", required=False, help="Governance domain ID")
|
|
1043
|
+
@click.option("--parent-id", required=False, help="Parent term ID (for hierarchical terms)")
|
|
1007
1044
|
@click.option(
|
|
1008
1045
|
"--status",
|
|
1009
1046
|
required=False,
|
|
@@ -1026,7 +1063,7 @@ def delete(term_id, force):
|
|
|
1026
1063
|
@click.option("--resource-url", required=False, help="Resource URL for additional reading (can be specified multiple times, replaces existing)", multiple=True)
|
|
1027
1064
|
@click.option("--add-acronym", required=False, help="Add acronym to existing ones (can be specified multiple times)", multiple=True)
|
|
1028
1065
|
@click.option("--add-owner-id", required=False, help="Add owner to existing ones (can be specified multiple times)", multiple=True)
|
|
1029
|
-
def update(term_id, name, description, domain_id, status, acronym, owner_id, resource_name, resource_url, add_acronym, add_owner_id):
|
|
1066
|
+
def update(term_id, name, description, domain_id, parent_id, status, acronym, owner_id, resource_name, resource_url, add_acronym, add_owner_id):
|
|
1030
1067
|
"""Update an existing Unified Catalog term."""
|
|
1031
1068
|
try:
|
|
1032
1069
|
client = UnifiedCatalogClient()
|
|
@@ -1040,6 +1077,8 @@ def update(term_id, name, description, domain_id, status, acronym, owner_id, res
|
|
|
1040
1077
|
args["--description"] = [description]
|
|
1041
1078
|
if domain_id:
|
|
1042
1079
|
args["--governance-domain-id"] = [domain_id]
|
|
1080
|
+
if parent_id:
|
|
1081
|
+
args["--parent-id"] = [parent_id]
|
|
1043
1082
|
if status:
|
|
1044
1083
|
args["--status"] = [status]
|
|
1045
1084
|
|
|
@@ -1070,13 +1109,555 @@ def update(term_id, name, description, domain_id, status, acronym, owner_id, res
|
|
|
1070
1109
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1071
1110
|
return
|
|
1072
1111
|
|
|
1073
|
-
console.print(f"[green]
|
|
1112
|
+
console.print(f"[green] SUCCESS:[/green] Updated glossary term '{term_id}'")
|
|
1074
1113
|
console.print(json.dumps(result, indent=2))
|
|
1075
1114
|
|
|
1076
1115
|
except Exception as e:
|
|
1077
1116
|
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1078
1117
|
|
|
1079
1118
|
|
|
1119
|
+
@term.command(name="import-csv")
|
|
1120
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="Path to CSV file with terms")
|
|
1121
|
+
@click.option("--domain-id", required=True, help="Governance domain ID for all terms")
|
|
1122
|
+
@click.option("--dry-run", is_flag=True, help="Preview terms without creating them")
|
|
1123
|
+
def import_terms_from_csv(csv_file, domain_id, dry_run):
|
|
1124
|
+
"""Bulk import glossary terms from a CSV file.
|
|
1125
|
+
|
|
1126
|
+
CSV Format:
|
|
1127
|
+
name,description,status,acronyms,owner_ids,resource_name,resource_url
|
|
1128
|
+
|
|
1129
|
+
- name: Required term name
|
|
1130
|
+
- description: Optional description
|
|
1131
|
+
- status: Draft, Published, or Archived (default: Draft)
|
|
1132
|
+
- acronyms: Comma-separated list (e.g., "API,REST")
|
|
1133
|
+
- owner_ids: Comma-separated list of Entra Object IDs
|
|
1134
|
+
- resource_name: Name of related resource
|
|
1135
|
+
- resource_url: URL of related resource
|
|
1136
|
+
|
|
1137
|
+
Multiple resources can be specified by separating with semicolons.
|
|
1138
|
+
"""
|
|
1139
|
+
try:
|
|
1140
|
+
client = UnifiedCatalogClient()
|
|
1141
|
+
|
|
1142
|
+
# Read and parse CSV
|
|
1143
|
+
terms = []
|
|
1144
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
1145
|
+
reader = csv.DictReader(f)
|
|
1146
|
+
for row in reader:
|
|
1147
|
+
term = {
|
|
1148
|
+
"name": row.get("name", "").strip(),
|
|
1149
|
+
"description": row.get("description", "").strip(),
|
|
1150
|
+
"status": row.get("status", "Draft").strip(),
|
|
1151
|
+
"domain_id": domain_id,
|
|
1152
|
+
"acronyms": [],
|
|
1153
|
+
"owner_ids": [],
|
|
1154
|
+
"resources": []
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
# Parse acronyms
|
|
1158
|
+
if row.get("acronyms"):
|
|
1159
|
+
term["acronyms"] = [a.strip() for a in row["acronyms"].split(",") if a.strip()]
|
|
1160
|
+
|
|
1161
|
+
# Parse owner IDs
|
|
1162
|
+
if row.get("owner_ids"):
|
|
1163
|
+
term["owner_ids"] = [o.strip() for o in row["owner_ids"].split(",") if o.strip()]
|
|
1164
|
+
|
|
1165
|
+
# Parse resources
|
|
1166
|
+
resource_names = row.get("resource_name", "").strip()
|
|
1167
|
+
resource_urls = row.get("resource_url", "").strip()
|
|
1168
|
+
|
|
1169
|
+
if resource_names and resource_urls:
|
|
1170
|
+
names = [n.strip() for n in resource_names.split(";") if n.strip()]
|
|
1171
|
+
urls = [u.strip() for u in resource_urls.split(";") if u.strip()]
|
|
1172
|
+
term["resources"] = [{"name": n, "url": u} for n, u in zip(names, urls)]
|
|
1173
|
+
|
|
1174
|
+
if term["name"]: # Only add if name is present
|
|
1175
|
+
terms.append(term)
|
|
1176
|
+
|
|
1177
|
+
if not terms:
|
|
1178
|
+
console.print("[yellow]No valid terms found in CSV file.[/yellow]")
|
|
1179
|
+
return
|
|
1180
|
+
|
|
1181
|
+
console.print(f"[cyan]Found {len(terms)} term(s) in CSV file[/cyan]")
|
|
1182
|
+
|
|
1183
|
+
if dry_run:
|
|
1184
|
+
console.print("\n[yellow]DRY RUN - Preview of terms to be created:[/yellow]\n")
|
|
1185
|
+
table = Table(title="Terms to Import")
|
|
1186
|
+
table.add_column("#", style="dim", width=4)
|
|
1187
|
+
table.add_column("Name", style="cyan")
|
|
1188
|
+
table.add_column("Status", style="yellow")
|
|
1189
|
+
table.add_column("Acronyms", style="magenta")
|
|
1190
|
+
table.add_column("Owners", style="green")
|
|
1191
|
+
|
|
1192
|
+
for i, term in enumerate(terms, 1):
|
|
1193
|
+
acronyms = ", ".join(term.get("acronyms", []))
|
|
1194
|
+
owners = ", ".join(term.get("owner_ids", []))
|
|
1195
|
+
table.add_row(
|
|
1196
|
+
str(i),
|
|
1197
|
+
term["name"],
|
|
1198
|
+
term["status"],
|
|
1199
|
+
acronyms or "-",
|
|
1200
|
+
owners or "-"
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
console.print(table)
|
|
1204
|
+
console.print(f"\n[dim]Domain ID: {domain_id}[/dim]")
|
|
1205
|
+
return
|
|
1206
|
+
|
|
1207
|
+
# Import terms (one by one using single POST)
|
|
1208
|
+
success_count = 0
|
|
1209
|
+
failed_count = 0
|
|
1210
|
+
failed_terms = []
|
|
1211
|
+
|
|
1212
|
+
with console.status("[bold green]Importing terms...") as status:
|
|
1213
|
+
for i, term in enumerate(terms, 1):
|
|
1214
|
+
status.update(f"[bold green]Creating term {i}/{len(terms)}: {term['name']}")
|
|
1215
|
+
|
|
1216
|
+
try:
|
|
1217
|
+
# Create individual term
|
|
1218
|
+
args = {
|
|
1219
|
+
"--name": [term["name"]],
|
|
1220
|
+
"--description": [term.get("description", "")],
|
|
1221
|
+
"--governance-domain-id": [term["domain_id"]],
|
|
1222
|
+
"--status": [term.get("status", "Draft")],
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if term.get("acronyms"):
|
|
1226
|
+
args["--acronym"] = term["acronyms"]
|
|
1227
|
+
|
|
1228
|
+
if term.get("owner_ids"):
|
|
1229
|
+
args["--owner-id"] = term["owner_ids"]
|
|
1230
|
+
|
|
1231
|
+
if term.get("resources"):
|
|
1232
|
+
args["--resource-name"] = [r["name"] for r in term["resources"]]
|
|
1233
|
+
args["--resource-url"] = [r["url"] for r in term["resources"]]
|
|
1234
|
+
|
|
1235
|
+
result = client.create_term(args)
|
|
1236
|
+
|
|
1237
|
+
# Check if result contains an ID (indicates successful creation)
|
|
1238
|
+
if result and isinstance(result, dict) and result.get("id"):
|
|
1239
|
+
success_count += 1
|
|
1240
|
+
term_id = result.get("id")
|
|
1241
|
+
console.print(f"[green]Created: {term['name']} (ID: {term_id})[/green]")
|
|
1242
|
+
elif result and not (isinstance(result, dict) and "error" in result):
|
|
1243
|
+
# Got a response but no ID - might be an issue
|
|
1244
|
+
console.print(f"[yellow]WARNING: Response received for {term['name']} but no ID returned[/yellow]")
|
|
1245
|
+
console.print(f"[dim]Response: {json.dumps(result, indent=2)[:200]}...[/dim]")
|
|
1246
|
+
failed_count += 1
|
|
1247
|
+
failed_terms.append({"name": term["name"], "error": "No ID in response"})
|
|
1248
|
+
else:
|
|
1249
|
+
failed_count += 1
|
|
1250
|
+
error_msg = result.get("error", "Unknown error") if isinstance(result, dict) else "No response"
|
|
1251
|
+
failed_terms.append({"name": term["name"], "error": error_msg})
|
|
1252
|
+
console.print(f"[red]FAILED: {term['name']} - {error_msg}[/red]")
|
|
1253
|
+
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
failed_count += 1
|
|
1256
|
+
failed_terms.append({"name": term["name"], "error": str(e)})
|
|
1257
|
+
console.print(f"[red]FAILED: {term['name']} - {str(e)}[/red]")
|
|
1258
|
+
|
|
1259
|
+
# Summary
|
|
1260
|
+
console.print("\n" + "="*60)
|
|
1261
|
+
console.print(f"[cyan]Import Summary:[/cyan]")
|
|
1262
|
+
console.print(f" Total terms: {len(terms)}")
|
|
1263
|
+
console.print(f" [green]Successfully created: {success_count}[/green]")
|
|
1264
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1265
|
+
|
|
1266
|
+
if failed_terms:
|
|
1267
|
+
console.print("\n[red]Failed Terms:[/red]")
|
|
1268
|
+
for ft in failed_terms:
|
|
1269
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1270
|
+
|
|
1271
|
+
except Exception as e:
|
|
1272
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
@term.command(name="import-json")
|
|
1276
|
+
@click.option("--json-file", required=True, type=click.Path(exists=True), help="Path to JSON file with terms")
|
|
1277
|
+
@click.option("--dry-run", is_flag=True, help="Preview terms without creating them")
|
|
1278
|
+
def import_terms_from_json(json_file, dry_run):
|
|
1279
|
+
"""Bulk import glossary terms from a JSON file.
|
|
1280
|
+
|
|
1281
|
+
JSON Format:
|
|
1282
|
+
[
|
|
1283
|
+
{
|
|
1284
|
+
"name": "Term Name",
|
|
1285
|
+
"description": "Description",
|
|
1286
|
+
"domain_id": "domain-guid",
|
|
1287
|
+
"status": "Draft",
|
|
1288
|
+
"acronyms": ["API", "REST"],
|
|
1289
|
+
"owner_ids": ["owner-guid-1"],
|
|
1290
|
+
"resources": [
|
|
1291
|
+
{"name": "Resource Name", "url": "https://example.com"}
|
|
1292
|
+
]
|
|
1293
|
+
}
|
|
1294
|
+
]
|
|
1295
|
+
|
|
1296
|
+
Each term must include domain_id.
|
|
1297
|
+
"""
|
|
1298
|
+
try:
|
|
1299
|
+
client = UnifiedCatalogClient()
|
|
1300
|
+
|
|
1301
|
+
# Read and parse JSON
|
|
1302
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
1303
|
+
terms = json.load(f)
|
|
1304
|
+
|
|
1305
|
+
if not isinstance(terms, list):
|
|
1306
|
+
console.print("[red]ERROR:[/red] JSON file must contain an array of terms")
|
|
1307
|
+
return
|
|
1308
|
+
|
|
1309
|
+
if not terms:
|
|
1310
|
+
console.print("[yellow]No terms found in JSON file.[/yellow]")
|
|
1311
|
+
return
|
|
1312
|
+
|
|
1313
|
+
console.print(f"[cyan]Found {len(terms)} term(s) in JSON file[/cyan]")
|
|
1314
|
+
|
|
1315
|
+
if dry_run:
|
|
1316
|
+
console.print("\n[yellow]DRY RUN - Preview of terms to be created:[/yellow]\n")
|
|
1317
|
+
_format_json_output(terms)
|
|
1318
|
+
return
|
|
1319
|
+
|
|
1320
|
+
# Import terms
|
|
1321
|
+
success_count = 0
|
|
1322
|
+
failed_count = 0
|
|
1323
|
+
failed_terms = []
|
|
1324
|
+
|
|
1325
|
+
with console.status("[bold green]Importing terms...") as status:
|
|
1326
|
+
for i, term in enumerate(terms, 1):
|
|
1327
|
+
term_name = term.get("name", f"Term {i}")
|
|
1328
|
+
status.update(f"[bold green]Creating term {i}/{len(terms)}: {term_name}")
|
|
1329
|
+
|
|
1330
|
+
try:
|
|
1331
|
+
args = {
|
|
1332
|
+
"--name": [term.get("name", "")],
|
|
1333
|
+
"--description": [term.get("description", "")],
|
|
1334
|
+
"--governance-domain-id": [term.get("domain_id", "")],
|
|
1335
|
+
"--status": [term.get("status", "Draft")],
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
if term.get("acronyms"):
|
|
1339
|
+
args["--acronym"] = term["acronyms"]
|
|
1340
|
+
|
|
1341
|
+
if term.get("owner_ids"):
|
|
1342
|
+
args["--owner-id"] = term["owner_ids"]
|
|
1343
|
+
|
|
1344
|
+
if term.get("resources"):
|
|
1345
|
+
args["--resource-name"] = [r.get("name", "") for r in term["resources"]]
|
|
1346
|
+
args["--resource-url"] = [r.get("url", "") for r in term["resources"]]
|
|
1347
|
+
|
|
1348
|
+
result = client.create_term(args)
|
|
1349
|
+
|
|
1350
|
+
# Check if result contains an ID (indicates successful creation)
|
|
1351
|
+
if result and isinstance(result, dict) and result.get("id"):
|
|
1352
|
+
success_count += 1
|
|
1353
|
+
term_id = result.get("id")
|
|
1354
|
+
console.print(f"[green]Created: {term_name} (ID: {term_id})[/green]")
|
|
1355
|
+
elif result and not (isinstance(result, dict) and "error" in result):
|
|
1356
|
+
# Got a response but no ID - might be an issue
|
|
1357
|
+
console.print(f"[yellow]WARNING: Response received for {term_name} but no ID returned[/yellow]")
|
|
1358
|
+
console.print(f"[dim]Response: {json.dumps(result, indent=2)[:200]}...[/dim]")
|
|
1359
|
+
failed_count += 1
|
|
1360
|
+
failed_terms.append({"name": term_name, "error": "No ID in response"})
|
|
1361
|
+
else:
|
|
1362
|
+
failed_count += 1
|
|
1363
|
+
error_msg = result.get("error", "Unknown error") if isinstance(result, dict) else "No response"
|
|
1364
|
+
failed_terms.append({"name": term_name, "error": error_msg})
|
|
1365
|
+
console.print(f"[red]FAILED: {term_name} - {error_msg}[/red]")
|
|
1366
|
+
|
|
1367
|
+
except Exception as e:
|
|
1368
|
+
failed_count += 1
|
|
1369
|
+
failed_terms.append({"name": term_name, "error": str(e)})
|
|
1370
|
+
console.print(f"[red]FAILED: {term_name} - {str(e)}[/red]")
|
|
1371
|
+
|
|
1372
|
+
# Summary
|
|
1373
|
+
console.print("\n" + "="*60)
|
|
1374
|
+
console.print(f"[cyan]Import Summary:[/cyan]")
|
|
1375
|
+
console.print(f" Total terms: {len(terms)}")
|
|
1376
|
+
console.print(f" [green]Successfully created: {success_count}[/green]")
|
|
1377
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1378
|
+
|
|
1379
|
+
if failed_terms:
|
|
1380
|
+
console.print("\n[red]Failed Terms:[/red]")
|
|
1381
|
+
for ft in failed_terms:
|
|
1382
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1383
|
+
|
|
1384
|
+
except Exception as e:
|
|
1385
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
@term.command(name="update-csv")
|
|
1389
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="Path to CSV file with term updates")
|
|
1390
|
+
@click.option("--dry-run", is_flag=True, help="Preview updates without applying them")
|
|
1391
|
+
def update_terms_from_csv(csv_file, dry_run):
|
|
1392
|
+
"""Bulk update glossary terms from a CSV file.
|
|
1393
|
+
|
|
1394
|
+
CSV Format:
|
|
1395
|
+
term_id,name,description,status,parent_id,acronyms,owner_ids,add_acronyms,add_owner_ids
|
|
1396
|
+
|
|
1397
|
+
Required:
|
|
1398
|
+
- term_id: The ID of the term to update
|
|
1399
|
+
|
|
1400
|
+
Optional (leave empty to skip update):
|
|
1401
|
+
- name: New term name (replaces existing)
|
|
1402
|
+
- description: New description (replaces existing)
|
|
1403
|
+
- status: New status (Draft, Published, Archived)
|
|
1404
|
+
- parent_id: Parent term ID for hierarchical relationships (replaces existing)
|
|
1405
|
+
- acronyms: New acronyms separated by semicolons (replaces all existing)
|
|
1406
|
+
- owner_ids: New owner IDs separated by semicolons (replaces all existing)
|
|
1407
|
+
- add_acronyms: Acronyms to add separated by semicolons (preserves existing)
|
|
1408
|
+
- add_owner_ids: Owner IDs to add separated by semicolons (preserves existing)
|
|
1409
|
+
|
|
1410
|
+
Example CSV:
|
|
1411
|
+
term_id,name,description,status,parent_id,add_acronyms,add_owner_ids
|
|
1412
|
+
abc-123,,Updated description,Published,parent-term-guid,API;REST,user1@company.com
|
|
1413
|
+
def-456,New Name,,,parent-term-guid,SQL,
|
|
1414
|
+
"""
|
|
1415
|
+
import csv
|
|
1416
|
+
|
|
1417
|
+
try:
|
|
1418
|
+
# Read CSV file
|
|
1419
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
1420
|
+
reader = csv.DictReader(f)
|
|
1421
|
+
updates = list(reader)
|
|
1422
|
+
|
|
1423
|
+
if not updates:
|
|
1424
|
+
console.print("[yellow]No updates found in CSV file.[/yellow]")
|
|
1425
|
+
return
|
|
1426
|
+
|
|
1427
|
+
console.print(f"Found {len(updates)} term(s) to update in CSV file")
|
|
1428
|
+
|
|
1429
|
+
# Dry run preview
|
|
1430
|
+
if dry_run:
|
|
1431
|
+
console.print("\n[cyan]DRY RUN - Preview of updates to be applied:[/cyan]\n")
|
|
1432
|
+
|
|
1433
|
+
table = Table(title="Terms to Update")
|
|
1434
|
+
table.add_column("#", style="cyan")
|
|
1435
|
+
table.add_column("Term ID", style="yellow")
|
|
1436
|
+
table.add_column("Updates", style="white")
|
|
1437
|
+
|
|
1438
|
+
for idx, update in enumerate(updates, 1):
|
|
1439
|
+
term_id = update.get('term_id', '').strip()
|
|
1440
|
+
if not term_id:
|
|
1441
|
+
continue
|
|
1442
|
+
|
|
1443
|
+
changes = []
|
|
1444
|
+
if update.get('name', '').strip():
|
|
1445
|
+
changes.append(f"name: {update['name']}")
|
|
1446
|
+
if update.get('description', '').strip():
|
|
1447
|
+
changes.append(f"desc: {update['description'][:50]}...")
|
|
1448
|
+
if update.get('status', '').strip():
|
|
1449
|
+
changes.append(f"status: {update['status']}")
|
|
1450
|
+
if update.get('parent_id', '').strip():
|
|
1451
|
+
changes.append(f"parent: {update['parent_id'][:20]}...")
|
|
1452
|
+
if update.get('acronyms', '').strip():
|
|
1453
|
+
changes.append(f"acronyms: {update['acronyms']}")
|
|
1454
|
+
if update.get('add_acronyms', '').strip():
|
|
1455
|
+
changes.append(f"add acronyms: {update['add_acronyms']}")
|
|
1456
|
+
if update.get('owner_ids', '').strip():
|
|
1457
|
+
changes.append(f"owners: {update['owner_ids']}")
|
|
1458
|
+
if update.get('add_owner_ids', '').strip():
|
|
1459
|
+
changes.append(f"add owners: {update['add_owner_ids']}")
|
|
1460
|
+
|
|
1461
|
+
table.add_row(str(idx), term_id[:36], ", ".join(changes) if changes else "No changes")
|
|
1462
|
+
|
|
1463
|
+
console.print(table)
|
|
1464
|
+
console.print(f"\n[yellow]Total terms to update: {len(updates)}[/yellow]")
|
|
1465
|
+
return
|
|
1466
|
+
|
|
1467
|
+
# Apply updates
|
|
1468
|
+
console.print("\n[cyan]Updating terms...[/cyan]\n")
|
|
1469
|
+
|
|
1470
|
+
client = UnifiedCatalogClient()
|
|
1471
|
+
success_count = 0
|
|
1472
|
+
failed_count = 0
|
|
1473
|
+
failed_terms = []
|
|
1474
|
+
|
|
1475
|
+
for idx, update in enumerate(updates, 1):
|
|
1476
|
+
term_id = update.get('term_id', '').strip()
|
|
1477
|
+
if not term_id:
|
|
1478
|
+
console.print(f"[yellow]Skipping row {idx}: Missing term_id[/yellow]")
|
|
1479
|
+
continue
|
|
1480
|
+
|
|
1481
|
+
# Build update arguments
|
|
1482
|
+
args = {"--term-id": [term_id]}
|
|
1483
|
+
|
|
1484
|
+
# Add replace operations
|
|
1485
|
+
if update.get('name', '').strip():
|
|
1486
|
+
args['--name'] = [update['name'].strip()]
|
|
1487
|
+
if update.get('description', '').strip():
|
|
1488
|
+
args['--description'] = [update['description'].strip()]
|
|
1489
|
+
if update.get('status', '').strip():
|
|
1490
|
+
args['--status'] = [update['status'].strip()]
|
|
1491
|
+
if update.get('parent_id', '').strip():
|
|
1492
|
+
args['--parent-id'] = [update['parent_id'].strip()]
|
|
1493
|
+
if update.get('acronyms', '').strip():
|
|
1494
|
+
args['--acronym'] = [a.strip() for a in update['acronyms'].split(';') if a.strip()]
|
|
1495
|
+
if update.get('owner_ids', '').strip():
|
|
1496
|
+
args['--owner-id'] = [o.strip() for o in update['owner_ids'].split(';') if o.strip()]
|
|
1497
|
+
|
|
1498
|
+
# Add "add" operations
|
|
1499
|
+
if update.get('add_acronyms', '').strip():
|
|
1500
|
+
args['--add-acronym'] = [a.strip() for a in update['add_acronyms'].split(';') if a.strip()]
|
|
1501
|
+
if update.get('add_owner_ids', '').strip():
|
|
1502
|
+
args['--add-owner-id'] = [o.strip() for o in update['add_owner_ids'].split(';') if o.strip()]
|
|
1503
|
+
|
|
1504
|
+
# Display progress
|
|
1505
|
+
display_name = update.get('name', term_id[:36])
|
|
1506
|
+
console.status(f"[{idx}/{len(updates)}] Updating: {display_name}...")
|
|
1507
|
+
|
|
1508
|
+
try:
|
|
1509
|
+
result = client.update_term(args)
|
|
1510
|
+
console.print(f"[green]SUCCESS:[/green] Updated term {idx}/{len(updates)}")
|
|
1511
|
+
success_count += 1
|
|
1512
|
+
except Exception as e:
|
|
1513
|
+
error_msg = str(e)
|
|
1514
|
+
console.print(f"[red]FAILED:[/red] {display_name}: {error_msg}")
|
|
1515
|
+
failed_terms.append({'term_id': term_id, 'name': display_name, 'error': error_msg})
|
|
1516
|
+
failed_count += 1
|
|
1517
|
+
|
|
1518
|
+
# Rate limiting
|
|
1519
|
+
time.sleep(0.2)
|
|
1520
|
+
|
|
1521
|
+
# Summary
|
|
1522
|
+
console.print("\n" + "="*60)
|
|
1523
|
+
console.print(f"[cyan]Update Summary:[/cyan]")
|
|
1524
|
+
console.print(f" Total terms: {len(updates)}")
|
|
1525
|
+
console.print(f" [green]Successfully updated: {success_count}[/green]")
|
|
1526
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1527
|
+
|
|
1528
|
+
if failed_terms:
|
|
1529
|
+
console.print("\n[red]Failed Updates:[/red]")
|
|
1530
|
+
for ft in failed_terms:
|
|
1531
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1532
|
+
|
|
1533
|
+
except Exception as e:
|
|
1534
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
@term.command(name="update-json")
|
|
1538
|
+
@click.option("--json-file", required=True, type=click.Path(exists=True), help="Path to JSON file with term updates")
|
|
1539
|
+
@click.option("--dry-run", is_flag=True, help="Preview updates without applying them")
|
|
1540
|
+
def update_terms_from_json(json_file, dry_run):
|
|
1541
|
+
"""Bulk update glossary terms from a JSON file.
|
|
1542
|
+
|
|
1543
|
+
JSON Format:
|
|
1544
|
+
{
|
|
1545
|
+
"updates": [
|
|
1546
|
+
{
|
|
1547
|
+
"term_id": "term-guid",
|
|
1548
|
+
"name": "New Name", // Optional: Replace name
|
|
1549
|
+
"description": "New description", // Optional: Replace description
|
|
1550
|
+
"status": "Published", // Optional: Change status
|
|
1551
|
+
"parent_id": "parent-term-guid", // Optional: Set parent term (hierarchical)
|
|
1552
|
+
"acronyms": ["API", "REST"], // Optional: Replace all acronyms
|
|
1553
|
+
"owner_ids": ["user@company.com"], // Optional: Replace all owners
|
|
1554
|
+
"add_acronyms": ["SQL"], // Optional: Add acronyms (preserves existing)
|
|
1555
|
+
"add_owner_ids": ["user2@company.com"] // Optional: Add owners (preserves existing)
|
|
1556
|
+
}
|
|
1557
|
+
]
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
Note: Leave fields empty or omit them to skip that update.
|
|
1561
|
+
"""
|
|
1562
|
+
import json
|
|
1563
|
+
|
|
1564
|
+
try:
|
|
1565
|
+
# Read JSON file
|
|
1566
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
1567
|
+
data = json.load(f)
|
|
1568
|
+
|
|
1569
|
+
updates = data.get('updates', [])
|
|
1570
|
+
|
|
1571
|
+
if not updates:
|
|
1572
|
+
console.print("[yellow]No updates found in JSON file.[/yellow]")
|
|
1573
|
+
return
|
|
1574
|
+
|
|
1575
|
+
console.print(f"Found {len(updates)} term(s) to update in JSON file")
|
|
1576
|
+
|
|
1577
|
+
# Dry run preview
|
|
1578
|
+
if dry_run:
|
|
1579
|
+
console.print("\n[cyan]DRY RUN - Preview of updates to be applied:[/cyan]\n")
|
|
1580
|
+
|
|
1581
|
+
# Display updates in colored JSON
|
|
1582
|
+
from rich.syntax import Syntax
|
|
1583
|
+
json_str = json.dumps(data, indent=2)
|
|
1584
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=True)
|
|
1585
|
+
console.print(syntax)
|
|
1586
|
+
|
|
1587
|
+
console.print(f"\n[yellow]Total terms to update: {len(updates)}[/yellow]")
|
|
1588
|
+
return
|
|
1589
|
+
|
|
1590
|
+
# Apply updates
|
|
1591
|
+
console.print("\n[cyan]Updating terms...[/cyan]\n")
|
|
1592
|
+
|
|
1593
|
+
client = UnifiedCatalogClient()
|
|
1594
|
+
success_count = 0
|
|
1595
|
+
failed_count = 0
|
|
1596
|
+
failed_terms = []
|
|
1597
|
+
|
|
1598
|
+
for idx, update in enumerate(updates, 1):
|
|
1599
|
+
term_id = update.get('term_id', '').strip() if isinstance(update.get('term_id'), str) else ''
|
|
1600
|
+
if not term_id:
|
|
1601
|
+
console.print(f"[yellow]Skipping update {idx}: Missing term_id[/yellow]")
|
|
1602
|
+
continue
|
|
1603
|
+
|
|
1604
|
+
# Build update arguments
|
|
1605
|
+
args = {"--term-id": [term_id]}
|
|
1606
|
+
|
|
1607
|
+
# Add replace operations
|
|
1608
|
+
if update.get('name'):
|
|
1609
|
+
args['--name'] = [update['name']]
|
|
1610
|
+
if update.get('description'):
|
|
1611
|
+
args['--description'] = [update['description']]
|
|
1612
|
+
if update.get('status'):
|
|
1613
|
+
args['--status'] = [update['status']]
|
|
1614
|
+
if update.get('parent_id'):
|
|
1615
|
+
args['--parent-id'] = [update['parent_id']]
|
|
1616
|
+
if update.get('acronyms'):
|
|
1617
|
+
args['--acronym'] = update['acronyms'] if isinstance(update['acronyms'], list) else [update['acronyms']]
|
|
1618
|
+
if update.get('owner_ids'):
|
|
1619
|
+
args['--owner-id'] = update['owner_ids'] if isinstance(update['owner_ids'], list) else [update['owner_ids']]
|
|
1620
|
+
|
|
1621
|
+
# Add "add" operations
|
|
1622
|
+
if update.get('add_acronyms'):
|
|
1623
|
+
args['--add-acronym'] = update['add_acronyms'] if isinstance(update['add_acronyms'], list) else [update['add_acronyms']]
|
|
1624
|
+
if update.get('add_owner_ids'):
|
|
1625
|
+
args['--add-owner-id'] = update['add_owner_ids'] if isinstance(update['add_owner_ids'], list) else [update['add_owner_ids']]
|
|
1626
|
+
|
|
1627
|
+
# Display progress
|
|
1628
|
+
display_name = update.get('name', term_id[:36])
|
|
1629
|
+
console.status(f"[{idx}/{len(updates)}] Updating: {display_name}...")
|
|
1630
|
+
|
|
1631
|
+
try:
|
|
1632
|
+
result = client.update_term(args)
|
|
1633
|
+
console.print(f"[green]SUCCESS:[/green] Updated term {idx}/{len(updates)}")
|
|
1634
|
+
success_count += 1
|
|
1635
|
+
except Exception as e:
|
|
1636
|
+
error_msg = str(e)
|
|
1637
|
+
console.print(f"[red]FAILED:[/red] {display_name}: {error_msg}")
|
|
1638
|
+
failed_terms.append({'term_id': term_id, 'name': display_name, 'error': error_msg})
|
|
1639
|
+
failed_count += 1
|
|
1640
|
+
|
|
1641
|
+
# Rate limiting
|
|
1642
|
+
time.sleep(0.2)
|
|
1643
|
+
|
|
1644
|
+
# Summary
|
|
1645
|
+
console.print("\n" + "="*60)
|
|
1646
|
+
console.print(f"[cyan]Update Summary:[/cyan]")
|
|
1647
|
+
console.print(f" Total terms: {len(updates)}")
|
|
1648
|
+
console.print(f" [green]Successfully updated: {success_count}[/green]")
|
|
1649
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
1650
|
+
|
|
1651
|
+
if failed_terms:
|
|
1652
|
+
console.print("\n[red]Failed Updates:[/red]")
|
|
1653
|
+
for ft in failed_terms:
|
|
1654
|
+
console.print(f" • {ft['name']}: {ft['error']}")
|
|
1655
|
+
|
|
1656
|
+
except Exception as e:
|
|
1657
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1080
1661
|
# ========================================
|
|
1081
1662
|
# OBJECTIVES AND KEY RESULTS (OKRs)
|
|
1082
1663
|
# ========================================
|
|
@@ -1132,7 +1713,7 @@ def create(definition, domain_id, status, owner_id, target_date):
|
|
|
1132
1713
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1133
1714
|
return
|
|
1134
1715
|
|
|
1135
|
-
console.print(f"[green]
|
|
1716
|
+
console.print(f"[green] SUCCESS:[/green] Created objective")
|
|
1136
1717
|
console.print(json.dumps(result, indent=2))
|
|
1137
1718
|
|
|
1138
1719
|
except Exception as e:
|
|
@@ -1275,7 +1856,7 @@ def create(name, description, domain_id, data_type, status, owner_id):
|
|
|
1275
1856
|
console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
|
|
1276
1857
|
return
|
|
1277
1858
|
|
|
1278
|
-
console.print(f"[green]
|
|
1859
|
+
console.print(f"[green] SUCCESS:[/green] Created critical data element '{name}'")
|
|
1279
1860
|
console.print(json.dumps(result, indent=2))
|
|
1280
1861
|
|
|
1281
1862
|
except Exception as e:
|
|
@@ -1362,7 +1943,7 @@ def show(cde_id):
|
|
|
1362
1943
|
|
|
1363
1944
|
|
|
1364
1945
|
# ========================================
|
|
1365
|
-
# HEALTH MANAGEMENT - IMPLEMENTED!
|
|
1946
|
+
# HEALTH MANAGEMENT - IMPLEMENTED!
|
|
1366
1947
|
# ========================================
|
|
1367
1948
|
|
|
1368
1949
|
# Import and register health commands from dedicated module
|