scripts/dashboard/validate_dashboards_format.py (117 lines of code) (raw):
'''This script will be triggered by a github action outlined in ~/.github/workflows/validate_dashboards_formar.yml.
It will be used to validate all new json and metadata files pertaining to integration sample dashboards.'''
import json
import os
import sys
import yaml
class MetadataFormattingError(Exception):
'''Raised when data in metadata file does not have expected format'''
pass
class JsonFormattingError(Exception):
'''Raised when data in json file or file name does not have expected format'''
pass
class MetadataInJsonMisMatchError(Exception):
'''Raised when data in metadata file does not have matching data in a json file'''
pass
class JsonInMetadataMisMatchError(Exception):
'''Raised when data in json file does not have matching data in a metadata file'''
pass
def validate_json(dashboards_data):
try:
dashboards_dict = json.load(dashboards_data)
except:
return {}
return dashboards_dict
def map_paths(paths):
'''Takes in list of paths and returns dictionary of metadata and dashboard json data,
organized by directories'''
map = {}
for path in paths:
path_parts = path.split('/')
# filter out non-dashboards files
if not 'dashboards' == path_parts[1] or len(path_parts) > 4:
continue
if not path_parts[2] in map:
map[path_parts[2]] = {
'metadata': {
},
'json_data': {
},
'json_files': []
}
if path_parts[3].split('.')[-1] == "json":
map[path_parts[2]]['json_files'].append(path_parts[3])
return map
def get_sample_dashboards_json(path):
'''Checks if file is in json format and returns data in dictionary form'''
with open(path) as f:
dashboards_dict = validate_json(f)
if not dashboards_dict:
raise JsonFormattingError("{} content could not be loaded".format(path))
return dashboards_dict
def check_json_file_name(path, file_name_parts):
'''Checks if json file name is in the proper format'''
if len(file_name_parts) != 2:
raise JsonFormattingError("{} file name not in <name>.json format".format(path))
def map_json_files(directory, path_map):
'''Stores json data into dictionary'''
for json_file in path_map[directory]['json_files']:
json_path = os.path.join('.', 'dashboards', directory, json_file)
dash_dict = get_sample_dashboards_json(json_path)
if not 'displayName' in dash_dict:
raise JsonFormattingError("{} is missing displayName field".format(json_file))
file_name_parts = json_file.split('.')
check_json_file_name(json_file, file_name_parts)
path_map[directory]['json_data'][file_name_parts[0]] = dash_dict['displayName']
def check_metadata_entries(path, sample_dashboard):
'''Assert sample dashboard metadata entry has all required fields.'''
required_fields = {"category", "id", "display_name", "description"}
missing_fields = required_fields - sample_dashboard.keys()
if missing_fields:
raise MetadataFormattingError("{} missing {}".format(path, missing_fields))
def check_metadata(directory, path_map):
'''Make following assertions about metadata.yam files:
* At least 1 sample dashboard exists
* Each dashboard entry has all required fields
* Each dashboard entry has a corresponding json template with same name & id
note: this function mutates the path_map dictionary'''
path = os.path.join('.', 'dashboards', directory, 'metadata.yaml')
with open(path) as f:
data = yaml.safe_load(f)
sample_dashboards = data.get("sample_dashboards")
if not sample_dashboards:
raise MetadataFormattingError("sample_dashboards not defined in {}".format(path))
for sample_dashboard in sample_dashboards:
# Call function to check metadata file fields
check_metadata_entries(path, sample_dashboard)
dashboard_id = sample_dashboard.get('id')
dashboard_name = sample_dashboard.get('display_name')
path_map[directory]['metadata'][dashboard_id] = dashboard_name
if not dashboard_id in path_map[directory]['json_data']:
raise MetadataInJsonMisMatchError("{} does not have a matching json file".format(dashboard_id))
if not dashboard_name == path_map[directory]['json_data'][dashboard_id]:
raise MetadataInJsonMisMatchError("{} does not have a matching json file".format(dashboard_name))
def check_json_in_metadata(directory, path_map):
'''Make following assertions about json template files:
* Template file name appears in metadata.yaml file
* Template display name matches corresponding id in metadata.yaml file
note: this function mutates the path_map dictionary'''
for json_file in path_map[directory]['json_files']:
json_path = os.path.join('.', 'dashboards', directory, json_file)
dash_dict = get_sample_dashboards_json(json_path)
json_id = json_file.split('.')[0]
if not json_id in path_map[directory]['metadata']:
raise JsonInMetadataMisMatchError("{} does not have a matching id in the metadata file".format(json_file))
if not dash_dict['displayName'] == path_map[directory]['metadata'][json_id]:
raise JsonInMetadataMisMatchError("{} does not have a matching display_name in the metadata file".format(json_file))
def check_directory(directory, path_map):
'''Parent function that calls top level processing and checking functions
Functions are order-dependant as the path_map dictionary is mutated by each function.'''
# Process json files into path map
map_json_files(directory, path_map)
# Metadata file check
check_metadata(directory, path_map)
# json file check
check_json_in_metadata(directory, path_map)
def main():
paths = sys.argv[1:]
# process paths into dictionary
path_map = map_paths(paths)
# run checks for each directory
for directory in path_map:
check_directory(directory, path_map)
if __name__ == '__main__':
main()