@@ -576,7 +576,10 @@ def create_dashboard_from_template(self, dashboard_name, template, scope, shared
576
576
scopeExpression = self .convert_scope_string_to_expression (scope )
577
577
if scopeExpression [0 ] == False :
578
578
return scopeExpression
579
- template ['scopeExpressionList' ] = map (lambda ex : {'operand' :ex ['operand' ], 'operator' :ex ['operator' ],'value' :ex ['value' ],'displayName' :'' , 'variable' :False }, scopeExpression [1 ])
579
+ if scopeExpression [1 ]:
580
+ template ['scopeExpressionList' ] = map (lambda ex : {'operand' : ex ['operand' ], 'operator' : ex ['operator' ], 'value' : ex ['value' ], 'displayName' : '' , 'variable' : False }, scopeExpression [1 ])
581
+ else :
582
+ template ['scopeExpressionList' ] = None
580
583
581
584
# NOTE: Individual panels might override the dashboard scope, the override will NOT be reset
582
585
if 'widgets' in template and template ['widgets' ] is not None :
@@ -596,6 +599,7 @@ def create_dashboard_from_template(self, dashboard_name, template, scope, shared
596
599
# Create the new dashboard
597
600
#
598
601
res = requests .post (self .url + self ._dashboards_api_endpoint , headers = self .hdrs , data = json .dumps ({'dashboard' : template }), verify = self .ssl_verify )
602
+
599
603
return self ._request_result (res )
600
604
601
605
def create_dashboard_from_view (self , newdashname , viewname , filter , shared = False , public = False ):
@@ -718,6 +722,15 @@ def create_dashboard_from_file(self, dashboard_name, filename, filter, shared=Fa
718
722
719
723
dashboard = loaded_object ['dashboard' ]
720
724
725
+ if loaded_object ['version' ] != self ._dashboards_api_version :
726
+ #
727
+ # Convert the dashboard (if possible)
728
+ #
729
+ conversion_result , dashboard = self ._convert_dashboard_to_current_version (dashboard , loaded_object ['version' ])
730
+
731
+ if conversion_result == False :
732
+ return conversion_result , dashboard
733
+
721
734
#
722
735
# Create the new dashboard
723
736
#
@@ -838,6 +851,258 @@ def convert_scope_string_to_expression(scope):
838
851
839
852
return [True , expressions ]
840
853
854
+ def _get_dashboard_converters (self ):
855
+ return {
856
+ 'v2' : {
857
+ 'v1' : self ._convert_dashboard_v1_to_v2
858
+ }
859
+ }
860
+
861
+ def _convert_dashboard_to_current_version (self , dashboard , version ):
862
+ converters_to = self ._get_dashboard_converters ().get (self ._dashboards_api_version , None )
863
+ if converters_to == None :
864
+ return False , 'unexpected error: no dashboard converters from version {} are supported' .format (self ._dashboards_api_version )
865
+
866
+ converter = converters_to .get (version , None )
867
+
868
+ if converter == None :
869
+ return False , 'dashboard version {} cannot be converted to {}' .format (version , self ._dashboards_api_version )
870
+
871
+ try :
872
+ return converter (dashboard )
873
+ except Exception as err :
874
+ return False , str (err )
875
+
876
+ def _convert_dashboard_v1_to_v2 (self , dashboard ):
877
+ #
878
+ # Migrations
879
+ #
880
+ # Each converter function will take:
881
+ # 1. name of the v1 dashboard property
882
+ # 2. v1 dashboard configuration
883
+ # 3. v2 dashboard configuration
884
+ #
885
+ # Each converter will apply changes to v2 dashboard configuration according to v1
886
+ #
887
+ def when_set (converter ):
888
+ def fn (prop_name , old_obj , new_obj ):
889
+ if prop_name in old_obj and old_obj [prop_name ] is not None :
890
+ converter (prop_name , old_obj , new_obj )
891
+
892
+ return fn
893
+
894
+ def with_default (converter , default = None ):
895
+ def fn (prop_name , old_obj , new_obj ):
896
+ if prop_name not in old_obj :
897
+ old_obj [prop_name ] = default
898
+
899
+ converter (prop_name , old_obj , new_obj )
900
+
901
+ return fn
902
+
903
+ def keep_as_is (prop_name , old_obj , new_obj ):
904
+ new_obj [prop_name ] = old_obj [prop_name ]
905
+
906
+ def drop_it (prop_name = None , old_obj = None , new_obj = None ):
907
+ pass
908
+
909
+ def ignore (prop_name = None , old_obj = None , new_obj = None ):
910
+ pass
911
+
912
+ def rename_to (new_prop_name ):
913
+ def rename (prop_name , old_obj , new_obj ):
914
+ new_obj [new_prop_name ] = old_obj [prop_name ]
915
+
916
+ return rename
917
+
918
+ def convert_schema (prop_name , old_dashboard , new_dashboard ):
919
+ new_dashboard [prop_name ] = 2
920
+
921
+ def convert_scope (prop_name , old_dashboard , new_dashboard ):
922
+ # # TODO!
923
+
924
+ scope = old_dashboard [prop_name ]
925
+ scope_conversion = self .convert_scope_string_to_expression (scope )
926
+
927
+ if scope_conversion [0 ]:
928
+ if scope_conversion [1 ]:
929
+ new_dashboard ['scopeExpressionList' ] = scope_conversion [1 ]
930
+ else :
931
+ # the property can be either `null` or a non-empty array
932
+ new_dashboard ['scopeExpressionList' ] = None
933
+ else :
934
+ raise SyntaxError ('scope not supported by the current grammar' )
935
+
936
+ def convert_events_filter (prop_name , old_dashboard , new_dashboard ):
937
+ rename_to ('eventsOverlaySettings' )(prop_name , old_dashboard , new_dashboard )
938
+
939
+ if 'showNotificationsDoNotFilterSameMetrics' in new_dashboard ['eventsOverlaySettings' ]:
940
+ del new_dashboard ['eventsOverlaySettings' ]['showNotificationsDoNotFilterSameMetrics' ]
941
+ if 'showNotificationsDoNotFilterSameScope' in new_dashboard ['eventsOverlaySettings' ]:
942
+ del new_dashboard ['eventsOverlaySettings' ]['showNotificationsDoNotFilterSameScope' ]
943
+
944
+ def convert_items (prop_name , old_dashboard , new_dashboard ):
945
+ def convert_color_coding (prop_name , old_widget , new_widget ):
946
+ best_value = None
947
+ worst_value = None
948
+ for item in old_widget [prop_name ]['thresholds' ]:
949
+ if item ['color' ] == 'best' :
950
+ best_value = item ['max' ] if not item ['max' ] else item ['min' ]
951
+ elif item ['color' ] == 'worst' :
952
+ worst_value = item ['min' ] if not item ['min' ] else item ['max' ]
953
+
954
+ if best_value is not None and worst_value is not None :
955
+ new_widget [prop_name ] = {
956
+ 'best' : best_value ,
957
+ 'worst' : worst_value
958
+ }
959
+
960
+ def convert_display_options (prop_name , old_widget , new_widget ):
961
+ keep_as_is (prop_name , old_widget , new_widget )
962
+
963
+ if 'yAxisScaleFactor' in new_widget [prop_name ]:
964
+ del new_widget [prop_name ]['yAxisScaleFactor' ]
965
+
966
+ def convert_group (prop_name , old_widget , new_widget ):
967
+ group_by_metrics = old_widget [prop_name ]['configuration' ]['groups' ][0 ]['groupBy' ]
968
+
969
+ migrated = []
970
+ for metric in group_by_metrics :
971
+ migrated .append ({ 'labelId' : metric ['metric' ] })
972
+
973
+ new_widget ['groupingLabelsIds' ] = migrated
974
+
975
+ def convert_override_filter (prop_name , old_widget , new_widget ):
976
+ if old_widget ['showAs' ] == 'map' :
977
+ # override scope always true if scope is set
978
+ new_widget ['overrideScope' ] = True
979
+ else :
980
+ new_widget ['overrideScope' ] = old_widget [prop_name ]
981
+
982
+
983
+ def convert_name (prop_name , old_widget , new_widget ):
984
+ #
985
+ # enforce unique name (on old dashboard, before migration)
986
+ #
987
+ unique_id = 1
988
+ name = old_widget [prop_name ]
989
+
990
+ for widget in old_dashboard ['items' ]:
991
+ if widget == old_widget :
992
+ break
993
+
994
+ if old_widget [prop_name ] == widget [prop_name ]:
995
+ old_widget [prop_name ] = '{} ({})' .format (name , unique_id )
996
+ unique_id += 1
997
+
998
+ keep_as_is (prop_name , old_widget , new_widget )
999
+
1000
+ def convert_metrics (prop_name , old_widget , new_widget ):
1001
+ def convert_property_name (prop_name , old_metric , new_metric ):
1002
+ keep_as_is (prop_name , old_metric , new_metric )
1003
+
1004
+ if old_metric ['metricId' ] == 'timestamp' :
1005
+ return 'k0'
1006
+
1007
+ metric_migrations = {
1008
+ 'metricId' : rename_to ('id' ),
1009
+ 'aggregation' : rename_to ('timeAggregation' ),
1010
+ 'groupAggregation' : rename_to ('groupAggregation' ),
1011
+ 'propertyName' : convert_property_name
1012
+ }
1013
+
1014
+ migrated_metrics = []
1015
+ for old_metric in old_widget [prop_name ]:
1016
+ migrated_metric = {}
1017
+
1018
+ for key in metric_migrations .keys ():
1019
+ if key in old_metric :
1020
+ metric_migrations [key ](key , old_metric , migrated_metric )
1021
+
1022
+ migrated_metrics .append (migrated_metric )
1023
+
1024
+ # Property name convention:
1025
+ # timestamp: k0 (if present)
1026
+ # other keys: k* (from 0 or 1, depending on timestamp)
1027
+ # values: v* (from 0)
1028
+ timestamp_key = filter (lambda m : m ['id' ] == 'timestamp' and not ('timeAggregation' in m and m ['timeAggregation' ] is not None ), migrated_metrics )
1029
+ no_timestamp_keys = filter (lambda m : m ['id' ] != 'timestamp' and not ('timeAggregation' in m and m ['timeAggregation' ] is not None ), migrated_metrics )
1030
+ values = filter (lambda m : 'timeAggregation' in m and m ['timeAggregation' ] is not None , migrated_metrics )
1031
+ if timestamp_key :
1032
+ timestamp_key [0 ]['propertyName' ] = 'k0'
1033
+ k_offset = 1 if timestamp_key else 0
1034
+ for i in range (0 , len (no_timestamp_keys )):
1035
+ no_timestamp_keys [i ]['propertyName' ] = 'k{}' .format (i + k_offset )
1036
+ for i in range (0 , len (values )):
1037
+ values [i ]['propertyName' ] = 'v{}' .format (i )
1038
+
1039
+ new_widget ['metrics' ] = migrated_metrics
1040
+
1041
+ widget_migrations = {
1042
+ 'colorCoding' : when_set (convert_color_coding ),
1043
+ 'compareToConfig' : when_set (keep_as_is ),
1044
+ 'customDisplayOptions' : with_default (convert_display_options , {}),
1045
+ 'gridConfiguration' : keep_as_is ,
1046
+ 'group' : when_set (convert_group ),
1047
+ 'hasTransparentBackground' : when_set (rename_to ('transparentBackground' )),
1048
+ 'limitToScope' : when_set (keep_as_is ),
1049
+ 'isPanelTitleVisible' : when_set (rename_to ('panelTitleVisible' )),
1050
+ 'markdownSource' : when_set (keep_as_is ),
1051
+ 'metrics' : with_default (convert_metrics , []),
1052
+ 'name' : with_default (convert_name , 'Panel' ),
1053
+ 'overrideFilter' : convert_override_filter ,
1054
+ 'paging' : drop_it ,
1055
+ 'scope' : with_default (keep_as_is , None ),
1056
+ 'showAs' : keep_as_is ,
1057
+ 'showAsType' : drop_it ,
1058
+ 'sorting' : drop_it ,
1059
+ 'textpanelTooltip' : when_set (keep_as_is ),
1060
+ }
1061
+
1062
+ migrated_widgets = []
1063
+ for old_widget in old_dashboard [prop_name ]:
1064
+ migrated_widget = {}
1065
+
1066
+ for key in widget_migrations .keys ():
1067
+ widget_migrations [key ](key , old_widget , migrated_widget )
1068
+
1069
+ migrated_widgets .append (migrated_widget )
1070
+
1071
+ new_dashboard ['widgets' ] = migrated_widgets
1072
+
1073
+ return migrated
1074
+
1075
+ migrations = {
1076
+ 'autoCreated' : keep_as_is ,
1077
+ 'createdOn' : keep_as_is ,
1078
+ 'eventsFilter' : with_default (convert_events_filter , {
1079
+ 'filterNotificationsUserInputFilter' : ''
1080
+ }),
1081
+ 'filterExpression' : convert_scope ,
1082
+ 'scopeExpressionList' : ignore , # scope will be generated from 'filterExpression'
1083
+ 'id' : keep_as_is ,
1084
+ 'isPublic' : rename_to ('public' ),
1085
+ 'isShared' : rename_to ('shared' ),
1086
+ 'items' : convert_items ,
1087
+ 'layout' : drop_it ,
1088
+ 'modifiedOn' : keep_as_is ,
1089
+ 'name' : keep_as_is ,
1090
+ 'publicToken' : drop_it ,
1091
+ 'schema' : convert_schema ,
1092
+ 'teamId' : keep_as_is ,
1093
+ 'username' : keep_as_is ,
1094
+ 'version' : keep_as_is ,
1095
+ }
1096
+
1097
+ #
1098
+ # Apply migrations
1099
+ #
1100
+ migrated = {}
1101
+ for key in migrations .keys ():
1102
+ migrations [key ](key , copy .deepcopy (dashboard ), migrated )
1103
+
1104
+ return True , migrated
1105
+
841
1106
842
1107
# For backwards compatibility
843
1108
SdcClient = SdMonitorClient
0 commit comments