cid-cmd 0.2.45__tar.gz → 0.2.47__tar.gz

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.
Files changed (127) hide show
  1. {cid-cmd-0.2.45/cid_cmd.egg-info → cid-cmd-0.2.47}/PKG-INFO +1 -1
  2. cid-cmd-0.2.47/cid/_version.py +2 -0
  3. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/base.py +6 -0
  4. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/cli.py +7 -1
  5. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/common.py +59 -19
  6. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/export.py +15 -3
  7. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/__init__.py +62 -9
  8. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/dashboard.py +10 -6
  9. cid-cmd-0.2.47/cid/test/python/test_isolated_parameters.py +12 -0
  10. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/utils.py +10 -0
  11. {cid-cmd-0.2.45 → cid-cmd-0.2.47/cid_cmd.egg-info}/PKG-INFO +1 -1
  12. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid_cmd.egg-info/SOURCES.txt +1 -0
  13. cid-cmd-0.2.45/cid/_version.py +0 -2
  14. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/LICENSE +0 -0
  15. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/MANIFEST.in +0 -0
  16. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/README.md +0 -0
  17. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/__init__.py +0 -0
  18. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/__init__.py +0 -0
  19. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/__init__.py +0 -0
  20. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cid/compute.json +0 -0
  21. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cid/ec2_running_cost.json +0 -0
  22. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cid/s3_view.json +0 -0
  23. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cid/summary_view.json +0 -0
  24. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/co/dataset.json +0 -0
  25. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cudos/hourly_view.json +0 -0
  26. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/cudos/resource_view.json +0 -0
  27. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/kpi/kpi_ebs_snap.json +0 -0
  28. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/kpi/kpi_ebs_storage_all.json +0 -0
  29. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/kpi/kpi_instance_all.json +0 -0
  30. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/kpi/kpi_s3_storage_all.json +0 -0
  31. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/kpi/kpi_tracker.json +0 -0
  32. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/shared/customer_all.json +0 -0
  33. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/tao/dataset.json +0 -0
  34. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/trends/daily_anomaly_detection.json +0 -0
  35. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/trends/monthly_anomaly_detection.json +0 -0
  36. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/datasets/trends/monthly_bill_by_account.json +0 -0
  37. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/permissions/dashboard_permissions.json +0 -0
  38. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/permissions/dashboard_permissions_namespace.json +0 -0
  39. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/permissions/data_set_permissions.json +0 -0
  40. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/permissions/data_source_permissions.json +0 -0
  41. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/permissions/folder_permissions.json +0 -0
  42. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/compute_savings_plan_eligible_spend.sql +0 -0
  43. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ec2_running_cost.sql +0 -0
  44. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ec2_running_cost_ri.sql +0 -0
  45. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ec2_running_cost_sp.sql +0 -0
  46. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ec2_running_cost_sp_ri.sql +0 -0
  47. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ri_sp_mapping.sql +0 -0
  48. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ri_sp_mapping_ri.sql +0 -0
  49. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ri_sp_mapping_sp.sql +0 -0
  50. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/ri_sp_mapping_sp_ri.sql +0 -0
  51. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/s3.sql +0 -0
  52. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/summary_view.sql +0 -0
  53. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/summary_view_ri.sql +0 -0
  54. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/summary_view_sp.sql +0 -0
  55. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cid/summary_view_sp_ri.sql +0 -0
  56. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/all_options.sql +0 -0
  57. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/auto_scale.json +0 -0
  58. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/auto_scale_options.sql +0 -0
  59. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/ebs_volume.json +0 -0
  60. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/ebs_volume_options.sql +0 -0
  61. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/ec2_instance.json +0 -0
  62. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/ec2_instance_options.sql +0 -0
  63. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/lambda.json +0 -0
  64. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/co/lambda_options.sql +0 -0
  65. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/hourly_view.sql +0 -0
  66. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/hourly_view_ri.sql +0 -0
  67. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/hourly_view_sp.sql +0 -0
  68. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/hourly_view_sp_ri.sql +0 -0
  69. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/resource_view.sql +0 -0
  70. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/resource_view_ri.sql +0 -0
  71. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/resource_view_sp.sql +0 -0
  72. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/cudos/resource_view_sp_ri.sql +0 -0
  73. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/first_kpi_instance_mapping_view.sql +0 -0
  74. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_ebs_snap_view.sql +0 -0
  75. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_ebs_storage_view.sql +0 -0
  76. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_instance_all_view.sql +0 -0
  77. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_instance_all_view_noRI.sql +0 -0
  78. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_instance_all_view_noRISP.sql +0 -0
  79. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_instance_all_view_noSP.sql +0 -0
  80. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/kpi_s3_storage_view.sql +0 -0
  81. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/kpi/last_kpi_tracker_view.sql +0 -0
  82. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/account_map.sql +0 -0
  83. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/account_map_dummy.sql +0 -0
  84. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/aws_accounts.sql +0 -0
  85. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/aws_regions.sql +0 -0
  86. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/aws_service_category_map.sql +0 -0
  87. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/business_units_map.sql +0 -0
  88. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/payer_account_name_map.sql +0 -0
  89. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/shared/ta_descriptions.sql +0 -0
  90. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/tao/glue_table.json +0 -0
  91. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/tao/ta_org_view.sql +0 -0
  92. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/daily_anomaly_detection.sql +0 -0
  93. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/monthly_anomaly_detection.sql +0 -0
  94. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/monthly_bill_by_account.sql +0 -0
  95. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/monthly_bill_by_account_ri.sql +0 -0
  96. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/monthly_bill_by_account_sp.sql +0 -0
  97. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/queries/trends/monthly_bill_by_account_sp_ri.sql +0 -0
  98. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/builtin/core/data/resources.yaml +0 -0
  99. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/commands/__init__.py +0 -0
  100. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/commands/command_base.py +0 -0
  101. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/commands/init_qs.py +0 -0
  102. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/exceptions.py +0 -0
  103. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/__init__.py +0 -0
  104. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/account_map.py +0 -0
  105. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/athena.py +0 -0
  106. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/csv2view.py +0 -0
  107. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/cur.py +0 -0
  108. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/diff.py +0 -0
  109. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/glue.py +0 -0
  110. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/organizations.py +0 -0
  111. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/dataset.py +0 -0
  112. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/datasource.py +0 -0
  113. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/resource.py +0 -0
  114. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/quicksight/template.py +0 -0
  115. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/randtime.py +0 -0
  116. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/s3.py +0 -0
  117. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/helpers/timezone.py +0 -0
  118. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/logger.py +0 -0
  119. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/plugin.py +0 -0
  120. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/test/python/test_csv2view.py +0 -0
  121. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid/test/python/test_merge.py +0 -0
  122. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid_cmd.egg-info/dependency_links.txt +0 -0
  123. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid_cmd.egg-info/entry_points.txt +0 -0
  124. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid_cmd.egg-info/requires.txt +0 -0
  125. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/cid_cmd.egg-info/top_level.txt +0 -0
  126. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/pyproject.toml +0 -0
  127. {cid-cmd-0.2.45 → cid-cmd-0.2.47}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cid-cmd
3
- Version: 0.2.45
3
+ Version: 0.2.47
4
4
  Summary: Cloud Intelligence Dashboards deployment helper tool
5
5
  Home-page: https://github.com/aws-samples/aws-cudos-framework-deployment
6
6
  Author: AWS CUDOS Team
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '0.2.47'
@@ -21,6 +21,12 @@ class CidBase():
21
21
  def account_id(self) -> str:
22
22
  return self.awsIdentity.get('Account')
23
23
 
24
+ @property
25
+ def domain(self) -> str:
26
+ if self.region.startswith('cn-'):
27
+ return 'amazonaws.cn'
28
+ return 'aws.amazon.com'
29
+
24
30
  @property
25
31
  def awsIdentity(self) -> dict:
26
32
  if not self._awsIdentity:
@@ -1,4 +1,6 @@
1
+ import os
1
2
  import logging
3
+ import platform
2
4
 
3
5
  import click
4
6
 
@@ -14,7 +16,6 @@ prog_name="CLOUD INTELLIGENCE DASHBOARDS (CID) CLI"
14
16
  print(f'{prog_name} {version}\n')
15
17
 
16
18
  if __version__ != latest_version and latest_version != 'UNDEFINED':
17
-
18
19
  print('\033[93mUPDATE AVAILABLE\033[0m')
19
20
  print(f'\033[93mA new version {latest_version} is available, please consider update cid-cmd package via pip\033[0m\n\n')
20
21
  logger.info(f'A new version {latest_version} is available, please consider update cid-cmd package via pip')
@@ -67,6 +68,11 @@ def cid_command(func):
67
68
  @click.option('-y', '--yes', help='confirm all', is_flag=True, default=False)
68
69
  @click.pass_context
69
70
  def main(ctx, **kwargs):
71
+
72
+ # enable color for windows terminal
73
+ if platform.system() == "Windows":
74
+ os.system('color') #nosec B605, B607
75
+
70
76
  ctx.obj = Cid(**kwargs)
71
77
 
72
78
 
@@ -23,7 +23,7 @@ from botocore.exceptions import ClientError, NoCredentialsError, CredentialRetri
23
23
  from cid import utils
24
24
  from cid.base import CidBase
25
25
  from cid.plugin import Plugin
26
- from cid.utils import get_parameter, get_parameters, set_parameters, unset_parameter, get_yesno_parameter, cid_print, isatty, merge_objects
26
+ from cid.utils import get_parameter, get_parameters, set_parameters, unset_parameter, get_yesno_parameter, cid_print, isatty, merge_objects, IsolatedParameters
27
27
  from cid.helpers.account_map import AccountMap
28
28
  from cid.helpers import Athena, CUR, Glue, QuickSight, Dashboard, Dataset, Datasource, csv2view, Organizations
29
29
  from cid.helpers.quicksight.template import Template as CidQsTemplate
@@ -45,7 +45,7 @@ class Cid():
45
45
  self.plugins = self.__loadPlugins()
46
46
  self._clients = dict()
47
47
  self._visited_views = [] # Views updated in the current session
48
- self.qs_url = 'https://{region}.quicksight.aws.amazon.com/sn/dashboards/{dashboard_id}'
48
+ self.qs_url = 'https://{region}.quicksight.{domain}/sn/dashboards/{dashboard_id}'
49
49
  self.all_yes = kwargs.get('yes')
50
50
  self.verbose = kwargs.get('verbose')
51
51
  set_parameters(kwargs, self.all_yes)
@@ -75,7 +75,8 @@ class Cid():
75
75
  logger.info(f'AWS profile name: {self.base.session.profile_name}')
76
76
  self.qs_url_params = {
77
77
  'account_id': self.base.account_id,
78
- 'region': self.base.session.region_name
78
+ 'region': self.base.session.region_name,
79
+ 'domain': self.base.domain,
79
80
  }
80
81
  except (NoCredentialsError, CredentialRetrievalError):
81
82
  raise CidCritical('Error: Not authenticated, please check AWS credentials')
@@ -630,24 +631,63 @@ class Cid():
630
631
  @command
631
632
  def status(self, dashboard_id, **kwargs):
632
633
  """Check QuickSight dashboard status"""
633
-
634
- if not dashboard_id:
635
- if not self.qs.dashboards:
636
- print('No deployed dashboards found')
637
- return
638
- dashboard_id = self.qs.select_dashboard(force=True)
634
+ next_selection = None
635
+ while next_selection != 'exit':
639
636
  if not dashboard_id:
640
- print('No dashboard selected')
641
- return
642
- dashboard = self.qs.discover_dashboard(dashboardId=dashboard_id)
643
- else:
637
+ if not self.qs.dashboards:
638
+ print('No deployed dashboards found')
639
+ return
640
+ dashboard_id = self.qs.select_dashboard(force=True)
641
+ if not dashboard_id:
642
+ print('No dashboard selected')
643
+ return
644
644
  dashboard = self.qs.discover_dashboard(dashboardId=dashboard_id)
645
645
 
646
- if dashboard is not None:
647
- dashboard.display_status()
648
- dashboard.display_url(self.qs_url, **self.qs_url_params)
649
- else:
650
- click.echo('not deployed.')
646
+ if dashboard is not None:
647
+ dashboard.display_status()
648
+ dashboard.display_url(self.qs_url, **self.qs_url_params)
649
+ with IsolatedParameters():
650
+ next_selection = get_parameter(
651
+ param_name=f'{dashboard.id}',
652
+ message="Please make a selection",
653
+ choices={
654
+ '[◀] Back': 'back',
655
+ '[↗] Open': 'open',
656
+ '[◴] Refresh datasets': 'refresh',
657
+ '[↺] Update dashboard': 'update',
658
+ '[✕] Exit': 'exit',
659
+ }
660
+ )
661
+ if next_selection == 'open':
662
+ self.open(dashboard.id, **kwargs)
663
+
664
+ elif next_selection == 'refresh':
665
+ dashboard.refresh_datasets()
666
+
667
+ elif next_selection == 'update':
668
+ if dashboard.latest:
669
+ if not get_yesno_parameter(
670
+ param_name=f'redeploy-{dashboard.id}',
671
+ message=f'\nThe selected dashboard {dashboard.id} is already on the latest version.\nDo you want to re-deploy it?',
672
+ default='no'):
673
+ logger.info(f'Not re-deploying {dashboard.id} as it is on latest version.\n')
674
+ continue
675
+ recursive = get_parameter(
676
+ param_name='recursive',
677
+ message=f'\nRecursive update the Datasets and Views in addition to the Dashboard update?\nATTENTION: This could lead to the loss of dataset customization.\nRecursive update?',
678
+ choices={
679
+ '[→] Simple Update (only dashboard)': 'simple',
680
+ '[⇶] Recursive Update (dashboard and all dependencies)': 'recursive',
681
+ }
682
+ ) == 'recursive'
683
+ logger.info(f'Updating dashboard: {dashboard.id} with Recursive = {recursive}')
684
+ self._deploy(dashboard_id, recursive=recursive, update=True)
685
+ logger.info('Rediscover dashboards after update')
686
+ self.qs.discover_dashboards()
687
+ self.qs.clear_dashboard_selection()
688
+ dashboard_id = None
689
+ else:
690
+ cid_print('not deployed.')
651
691
 
652
692
  @command
653
693
  def delete(self, dashboard_id, **kwargs):
@@ -1284,7 +1324,7 @@ class Cid():
1284
1324
  athena_datasource = Datasource(raw={
1285
1325
  'AthenaParameters':{},
1286
1326
  "Id": datasource_id,
1287
- "Arn": f"arn:aws:quicksight:{self.base.session.region_name}:{self.base.account_id}:datasource/{datasource_id}",
1327
+ "Arn": f"arn:{self.base.partition}:quicksight:{self.base.session.region_name}:{self.base.account_id}:datasource/{datasource_id}",
1288
1328
  })
1289
1329
  except Exception as exc:
1290
1330
  raise CidCritical(
@@ -69,6 +69,16 @@ def choose_analysis(qs):
69
69
  return choices[choice]['AnalysisId']
70
70
 
71
71
 
72
+ def get_theme(analysis):
73
+ theme_arn = analysis.get('ThemeArn')
74
+ if not theme_arn:
75
+ return None
76
+ if theme_arn.startswith('arn:aws:quicksight::aws:theme/'):
77
+ return theme_arn.split('/')[-1]
78
+ logger.warning(f'Theme {theme_arn} is not standard and is not supported yet. Theme will be ignored.')
79
+ return None
80
+
81
+
72
82
  def export_analysis(qs, athena):
73
83
  """ Export analysis to yaml resource File
74
84
  """
@@ -94,7 +104,7 @@ def export_analysis(qs, athena):
94
104
  resources = {}
95
105
  resources['dashboards'] = {}
96
106
  resources['datasets'] = {}
97
-
107
+ theme_id = get_theme(analysis)
98
108
 
99
109
  cur_helper = CUR(session=athena.session)
100
110
  resources_datasets = []
@@ -270,6 +280,8 @@ def export_analysis(qs, athena):
270
280
  dashboard_resource['name'] = analysis['Name']
271
281
  dashboard_resource['dashboardId'] = dashboard_id
272
282
  dashboard_resource['category'] = get_parameters().get('category', 'Custom')
283
+ if theme_id:
284
+ dashboard_resource['theme'] = theme_id
273
285
 
274
286
  dashboard_export_method = None
275
287
  if get_parameters().get('template-id'):
@@ -344,7 +356,7 @@ def export_analysis(qs, athena):
344
356
  TemplateId=template_id,
345
357
  GrantPermissions=[
346
358
  {
347
- "Principal": f'arn:aws:iam::{reader_account_id}:root' if reader_account_id != '*' else '*',
359
+ "Principal": f'arn:{qs.partition}:iam::{reader_account_id}:root' if reader_account_id != '*' else '*',
348
360
  'Actions': [
349
361
  "quicksight:DescribeTemplate",
350
362
  ]
@@ -363,7 +375,7 @@ def export_analysis(qs, athena):
363
375
 
364
376
  for dataset in definition.get('DataSetIdentifierDeclarations', []):
365
377
  # Hide region and account number of the source account
366
- dataset["DataSetArn"] = 'arn:aws:quicksight:::dataset/' + dataset["DataSetArn"].split('/')[-1]
378
+ dataset["DataSetArn"] = f'arn:{qs.partition}:quicksight:::dataset/' + dataset["DataSetArn"].split('/')[-1]
367
379
  dashboard_resource['data'] = yaml.safe_dump(definition)
368
380
 
369
381
  resources['dashboards'][analysis['Name'].upper()] = dashboard_resource
@@ -2,6 +2,7 @@ import re
2
2
  import json
3
3
  import uuid
4
4
  import time
5
+ import datetime
5
6
  import logging
6
7
  from string import Template
7
8
  from typing import Dict, List, Union
@@ -15,7 +16,7 @@ from cid.helpers.quicksight.dashboard import Dashboard
15
16
  from cid.helpers.quicksight.dataset import Dataset
16
17
  from cid.helpers.quicksight.datasource import Datasource
17
18
  from cid.helpers.quicksight.template import Template as CidQsTemplate
18
- from cid.utils import get_parameter, get_parameters, exec_env, cid_print, ago
19
+ from cid.utils import get_parameter, get_parameters, exec_env, cid_print, ago, unset_parameter
19
20
  from cid.exceptions import CidCritical, CidError
20
21
 
21
22
  logger = logging.getLogger(__name__)
@@ -604,6 +605,10 @@ class QuickSight(CidBase):
604
605
  logger.debug(exc, exc_info=True)
605
606
  return list()
606
607
 
608
+ def clear_dashboard_selection (self):
609
+ """ Clears the current dashboard selection. """
610
+ unset_parameter('dashboard-id')
611
+
607
612
  def select_dashboard(self, force=False) -> str:
608
613
  """ Select from a list of discovered dashboards """
609
614
  dashboard_id = get_parameters().get('dashboard-id')
@@ -614,8 +619,9 @@ class QuickSight(CidBase):
614
619
  return None
615
620
  choices = {}
616
621
  for dashboard in self.dashboards.values():
617
- health = 'healthy' if dashboard.health else 'unhealthy'
618
- key = f'{dashboard.name} ({dashboard.arn}, {health}, {dashboard.status})'
622
+ health = '' if dashboard.health else ' UNHEALTHY'
623
+ status = '' if dashboard.status == 'up to date' else ' ' + dashboard.status.upper()
624
+ key = f'{dashboard.name} ({dashboard.arn.split("/")[-1]}){health}{status}'
619
625
  notice = dashboard.definition.get('deprecationNotice', '')
620
626
  if notice:
621
627
  key = f'{key} {notice}'
@@ -950,6 +956,29 @@ class QuickSight(CidBase):
950
956
  logger.debug(exc, exc_info=True)
951
957
  logger.info('No datasets found')
952
958
 
959
+ def refresh_dataset(self, dataset_id):
960
+ """ Refresh the dataset """
961
+
962
+ logger.info(f'Starting refresh for dataset: {dataset_id}')
963
+ status = 'FAILED'
964
+ try:
965
+ response = self.client.describe_data_set(
966
+ AwsAccountId=self.account_id,
967
+ DataSetId=dataset_id)
968
+ mode = response.get('DataSet').get('ImportMode')
969
+ if mode == 'DIRECT_QUERY':
970
+ return mode, 'DIRECT'
971
+ response = self.client.create_ingestion(
972
+ DataSetId=dataset_id,
973
+ IngestionId=datetime.datetime.now().strftime("%d%m%y-%H%M%S-%f"),
974
+ AwsAccountId=self.account_id)
975
+ status = response.get('IngestionStatus')
976
+ except self.client.exceptions.AccessDeniedException:
977
+ logger.error(f'Access denied refreshing dataset: {dataset_id}')
978
+ except Exception as exc:
979
+ logger.debug(exc, exc_info=True)
980
+ raise CidError(f'Unable to list refresh dataset {dataset_id}: {str(exc)}') from exc
981
+ return mode, status
953
982
 
954
983
  def describe_data_source(self, id: str, update: bool=False) -> Datasource:
955
984
  """ Describes an AWS QuickSight DataSource """
@@ -1125,12 +1154,27 @@ class QuickSight(CidBase):
1125
1154
  try:
1126
1155
  existing_schedules = self.get_dataset_refresh_schedules(dataset_id)
1127
1156
  except CidError as exc:
1128
- logger.debug(f'List refresh schedule throws: {exc}')
1129
- logger.warning(
1130
- f'Cannot read dataset schedules for dataset = {dataset_id}. {str(exc)}. Skipping schedule management.'
1131
- ' Please make sure scheduled refresh is configured manually.'
1132
- )
1133
- return
1157
+ # We cannot access schedules, but let's check if there are scheduled ingestions.
1158
+ ingestions_exist = False
1159
+ try:
1160
+ ingestions_exist = list(
1161
+ self.client.get_paginator('list_ingestions').paginate(
1162
+ DataSetId=dataset_id,
1163
+ AwsAccountId=self.account_id
1164
+ ).search("Ingestions[?RequestSource=='SCHEDULED']")
1165
+ )
1166
+ except Exception:
1167
+ logger.debug(f'List refresh schedule throws: {exc}')
1168
+ logger.warning(
1169
+ f'Cannot read dataset schedules for dataset = {dataset_id}. {str(exc)}. Skipping schedule management.'
1170
+ ' Please make sure scheduled refresh is configured manually.'
1171
+ )
1172
+ return
1173
+ if ingestions_exist:
1174
+ logger.debug(f'We cannot read schedules but there are ingestions. Skipping creation of schedule.')
1175
+ return
1176
+ logger.debug(f'We cannot read schedules but there no ingestions. Continue to creation of schedule.')
1177
+ existing_schedules = []
1134
1178
 
1135
1179
  if schedules:
1136
1180
  if exec_env()['terminal'] in ('lambda'):
@@ -1198,6 +1242,8 @@ class QuickSight(CidBase):
1198
1242
  logger.error(f'Unable to create refresh schedule with id {schedule["ScheduleId"]}. Dataset {dataset_id} does not exist.')
1199
1243
  except self.client.exceptions.AccessDeniedException:
1200
1244
  logger.error(f'Unable to create refresh schedule with id {schedule["ScheduleId"]}. Please add quicksight:CreateDataSet permission.')
1245
+ except self.client.exceptions.ResourceExistsException:
1246
+ logger.info(f'Schedule with id {schedule["ScheduleId"]} exists. But can have other settings. You better check.')
1201
1247
  except Exception as exc:
1202
1248
  logger.error(f'Unable to create refresh schedule with id {schedule["ScheduleId"]} for dataset "{dataset_id}": {str(exc)}')
1203
1249
  else:
@@ -1271,6 +1317,13 @@ class QuickSight(CidBase):
1271
1317
  'Name': definition.get('name'),
1272
1318
  'ValidationStrategy': {'Mode': 'LENIENT'},
1273
1319
  }
1320
+ theme = definition.get('theme')
1321
+ if theme:
1322
+ if not theme.startswith('arn:'):
1323
+ theme_arn = 'arn:aws:quicksight::aws:theme/' + theme
1324
+ else:
1325
+ raise NotImplementedError('Only standard themes are supported now.')
1326
+ create_parameters['ThemeArn'] = theme_arn
1274
1327
 
1275
1328
  if definition.get('sourceTemplate'):
1276
1329
  dataset_references = [
@@ -81,7 +81,7 @@ class Dashboard(CidQsResource):
81
81
  if self.version.get('Status') not in ['CREATION_SUCCESSFUL']:
82
82
  self._status = 'broken'
83
83
  self.status_detail = f"{self.version.get('Status')}: {self.version.get('Errors')}"
84
- # Not dicovered yet
84
+ # Not discovered yet
85
85
  elif not self.definition:
86
86
  self._status = 'undiscovered'
87
87
  # Missing dataset
@@ -147,13 +147,17 @@ class Dashboard(CidQsResource):
147
147
  if self.datasets:
148
148
  cid_print(f" <BOLD>Datasets:<END>")
149
149
  for dataset_name, dataset_id in sorted(self.datasets.items()):
150
- status = self.qs.get_dataset_last_ingestion(dataset_id) or '<BLUE>DIRECT<END>'
150
+ status = self.qs.get_dataset_last_ingestion(dataset_id) or '<BLUE>DIRECT<END>' #todo fix this Blue using dataset import type.
151
151
  cid_print(f' {dataset_name: <36} ({dataset_id: <36}) {status}')
152
152
 
153
- print('\n')
154
- if get_yesno_parameter('display-raw', 'Display dashboard raw data?', default='yes'):
155
- print(json.dumps(self.raw, indent=4, sort_keys=True, default=str))
156
-
157
153
  def display_url(self, url_template: str, launch: bool = False, **kwargs) -> None:
158
154
  url = url_template.format(dashboard_id=self.id, **kwargs)
159
155
  print(f"#######\n####### {self.name} is available at: " + url + "\n#######")
156
+
157
+ def refresh_datasets(self) -> None:
158
+ """Refresh datasets of dashboard"""
159
+ if self.datasets:
160
+ cid_print(f" <BOLD>Refreshing Datasets:<END>")
161
+ for dataset_name, dataset_id in sorted(self.datasets.items()):
162
+ mode, status = self.qs.refresh_dataset(dataset_id)
163
+ cid_print(f' {dataset_name: <36} ({dataset_id: <36}) Refresh Status: {status} Mode: {mode}')
@@ -0,0 +1,12 @@
1
+ from cid.utils import IsolatedParameters, get_parameters, set_parameters
2
+
3
+ def test_isolated_parameter_context():
4
+ """ make sure the isolated_parameter works
5
+ """
6
+ set_parameters({'param': 'a'})
7
+
8
+ with IsolatedParameters():
9
+ set_parameters({'param': 'b'})
10
+ assert get_parameters().get('param') == 'b', 'parameters within context must be B'
11
+
12
+ assert get_parameters().get('param') == 'a', 'parameters within context must be A'
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ import copy
3
4
  import math
4
5
  import inspect
5
6
  import logging
@@ -279,6 +280,15 @@ def ago(time):
279
280
  return 'just now'
280
281
 
281
282
 
283
+ class IsolatedParameters:
284
+ """A context manager to run something in isolated set of parameters"""
285
+ def __enter__(self):
286
+ self.backup = copy.deepcopy(params)
287
+
288
+ def __exit__(self, exc_type, exc_value, traceback):
289
+ global params
290
+ params = self.backup
291
+
282
292
  def merge_objects(obj1, obj2, depth=2):
283
293
  """ merging objects with a depth
284
294
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cid-cmd
3
- Version: 0.2.45
3
+ Version: 0.2.47
4
4
  Summary: Cloud Intelligence Dashboards deployment helper tool
5
5
  Home-page: https://github.com/aws-samples/aws-cudos-framework-deployment
6
6
  Author: AWS CUDOS Team
@@ -115,6 +115,7 @@ cid/helpers/quicksight/datasource.py
115
115
  cid/helpers/quicksight/resource.py
116
116
  cid/helpers/quicksight/template.py
117
117
  cid/test/python/test_csv2view.py
118
+ cid/test/python/test_isolated_parameters.py
118
119
  cid/test/python/test_merge.py
119
120
  cid_cmd.egg-info/PKG-INFO
120
121
  cid_cmd.egg-info/SOURCES.txt
@@ -1,2 +0,0 @@
1
-
2
- __version__ = '0.2.45'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes