OPMAAS-4530, OPMAAS-4534 Bug fixes and inline comments

master
Patryk Gudalewicz 2023-07-18 12:14:37 +02:00
parent 272cf14224
commit cab7660a8d
7 changed files with 52 additions and 26 deletions

View File

@ -1,3 +1,4 @@
#Main dashboard generation script
import yaml import yaml
from decouple import config from decouple import config
import json import json
@ -79,8 +80,13 @@ def main():
dashboard_json.append(createImageTile(config)) dashboard_json.append(createImageTile(config))
dashboard_json = dashboard_json + createHeaderTiles(config,args.wall) dashboard_json = dashboard_json + createHeaderTiles(config,args.wall)
boundindex = 1 boundindex = 1
#Load SLO config as object from yaml definition
sloconfigs = getSloConfigurations(configuration, script_config) sloconfigs = getSloConfigurations(configuration, script_config)
#Generate tile row including description, single value and graph tiles
dashboard_json = dashboard_json + createSloTileRow(boundindex,script_config,args.wall,sloconfigs,doc) dashboard_json = dashboard_json + createSloTileRow(boundindex,script_config,args.wall,sloconfigs,doc)
#Increment row index
boundindex = boundindex+1
#Save tile JSON to file
if rowcount == 0 or (args.rows is not None and boundindex%args.rows != 0): if rowcount == 0 or (args.rows is not None and boundindex%args.rows != 0):
with open("./tiles/dashboard_tiles_"+str(dahboardcount)+".json", "w") as file: with open("./tiles/dashboard_tiles_"+str(dahboardcount)+".json", "w") as file:
json.dump(dashboard_json, file, indent=2) json.dump(dashboard_json, file, indent=2)

View File

@ -1,3 +1,4 @@
#Single value tile definition (for json parse purpose)
class SingleValueTile: class SingleValueTile:
def __init__(self,name,bounds,timeframe,remoteEnvironmentUrl,metricSelector,graphThreshold): def __init__(self,name,bounds,timeframe,remoteEnvironmentUrl,metricSelector,graphThreshold):
self.name = name self.name = name
@ -28,7 +29,7 @@ class SingleValueTile:
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" }, "tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } } "graphChartSettings": { "connectNulls": "false" } }
self.queriesSettings = { "resolution": "","foldAggregation": "AVG" } self.queriesSettings = { "resolution": "","foldAggregation": "AVG" }
#Graph tile definition (for json parse purpose)
class GraphTile: class GraphTile:
def __init__(self,name,bounds,remoteEnvironmentUrl,metricSelector, countMetricSelector, responseMetricSelector,graphThreshold,customName,axisTargetMin,axisTargetMax,config): def __init__(self,name,bounds,remoteEnvironmentUrl,metricSelector, countMetricSelector, responseMetricSelector,graphThreshold,customName,axisTargetMin,axisTargetMax,config):
self.name = name self.name = name

View File

@ -1,7 +1,9 @@
#Library includes funtions used for interacting with Dashboard API
import requests import requests
from datetime import datetime from datetime import datetime
import os import os
import json import json
#HTTP request definition
def make_request(url, DTAPIToken,verify, method, jsondata): def make_request(url, DTAPIToken,verify, method, jsondata):
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -27,7 +29,7 @@ def make_request(url, DTAPIToken,verify, method, jsondata):
return "An Unknown Error occurred" + repr(err) return "An Unknown Error occurred" + repr(err)
return response return response
#Downloads dashboards from provided environment with given name
def get_all_dashboards_withname(DTAPIToken, DTENV,name): def get_all_dashboards_withname(DTAPIToken, DTENV,name):
DTAPIURL= DTENV + "api/config/v1/dashboards" DTAPIURL= DTENV + "api/config/v1/dashboards"
r = make_request(DTAPIURL,DTAPIToken,True,"get",None) r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
@ -40,7 +42,7 @@ def get_all_dashboards_withname(DTAPIToken, DTENV,name):
result.append(dashboard) result.append(dashboard)
result = sorted(result, key=lambda x : x['name'], reverse=False) result = sorted(result, key=lambda x : x['name'], reverse=False)
return result return result
#Stores current dashboard json files in archive repo
def backup_dashboards(DTAPIToken, DTENV, dashboards): def backup_dashboards(DTAPIToken, DTENV, dashboards):
for dashboard in dashboards: for dashboard in dashboards:
DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"] DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"]
@ -54,13 +56,13 @@ def backup_dashboards(DTAPIToken, DTENV, dashboards):
os.makedirs("./archive/"+strnowdate) os.makedirs("./archive/"+strnowdate)
with open("./archive/"+strnowdate+"/"+entityResponse["dashboardMetadata"]["name"]+"_"+strnow+".json", "w") as file: with open("./archive/"+strnowdate+"/"+entityResponse["dashboardMetadata"]["name"]+"_"+strnow+".json", "w") as file:
json.dump(entityResponse, file, indent=2) json.dump(entityResponse, file, indent=2)
#Deletes dashboards from remote tenant
def remove_dashboards(DTAPIToken, DTENV, dashboards): def remove_dashboards(DTAPIToken, DTENV, dashboards):
for dashboard in dashboards: for dashboard in dashboards:
print("Removing STAGING dashboard from Dynatrace: "+dashboard["name"]) print("Removing STAGING dashboard from Dynatrace: "+dashboard["name"])
DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"] DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"]
print(make_request(DTAPIURL,DTAPIToken,True,"delete",None)) print(make_request(DTAPIURL,DTAPIToken,True,"delete",None))
#Creates or updates staging dashboard on given tenant
def create_or_update_dashboard(DTAPIToken, DTENV, dashboards, files, businessline, config, department): def create_or_update_dashboard(DTAPIToken, DTENV, dashboards, files, businessline, config, department):
if(files): if(files):
for index, filename in enumerate(files,start=1): for index, filename in enumerate(files,start=1):

View File

@ -1,15 +1,17 @@
#Library includes funtions for interacting with git repos
from git import Repo from git import Repo
import os import os
#Clones repo if not exists
def clone_repo_if_notexist(repourl, reponame): def clone_repo_if_notexist(repourl, reponame):
if(not os.path.isdir(reponame)): if(not os.path.isdir(reponame)):
repo = Repo.clone_from(repourl, reponame) repo = Repo.clone_from(repourl, reponame)
return repo return repo
return Repo(reponame) return Repo(reponame)
#Performs pull on existing repo
def pull_repo(repo): def pull_repo(repo):
origin = repo.remotes.origin origin = repo.remotes.origin
origin.pull() origin.pull()
#Performs add, commit and push on existing repo
def push_repo(repo, message): def push_repo(repo, message):
repo.git.add(all=True) repo.git.add(all=True)
repo.index.commit(message) repo.index.commit(message)

View File

@ -1,4 +1,5 @@
from tileHelper import get_dataExplorerTileSloThreshold #Library includes definition and function for parsing SLO yaml definition into object
from tileFactory import get_dataExplorerTileSloThreshold
class Sloconfig: class Sloconfig:
def __init__(self): def __init__(self):
self.hub = "" self.hub = ""
@ -20,7 +21,7 @@ class Sloconfig:
self.slourl = "" self.slourl = ""
self.docurl = "" self.docurl = ""
#Loads values from yaml config files and parses them into object
def getSloConfigurations(sloconfig, scriptconfig): def getSloConfigurations(sloconfig, scriptconfig):
configs = [] configs = []
for hub in sloconfig["hubs"]: for hub in sloconfig["hubs"]:

View File

@ -1,18 +1,21 @@
#Library includes functions for interacting with SLOs
import yaml import yaml
from remoteDashboardHelper import make_request from remoteDashboard import make_request
from KRParser import krparser from KRParser import krparser
from decouple import config from decouple import config
#Loads SLOs definitions from yaml file
def load_slo_parameter(path): def load_slo_parameter(path):
with open(path) as file: with open(path) as file:
slo_doc = yaml.safe_load(file) slo_doc = yaml.safe_load(file)
return slo_doc return slo_doc
#Downloads SLO definition from Dynatrace API
def getSLO(env, envurl,sloid, DTAPIToken): def getSLO(env, envurl,sloid, DTAPIToken):
url = envurl+"/api/v2/slo/"+sloid+"?timeFrame=CURRENT" url = envurl+"/api/v2/slo/"+sloid+"?timeFrame=CURRENT"
response = make_request(url, DTAPIToken,True, "get", "") response = make_request(url, DTAPIToken,True, "get", "")
responseobj = response.json() responseobj = response.json()
responseobj["env"] = env responseobj["env"] = env
return responseobj return responseobj
#Generates Request Count selector for given service names
def getSloReqCountSelector(service_names): def getSloReqCountSelector(service_names):
selector = "" selector = ""
if(service_names["selectortype"] == "KR"): if(service_names["selectortype"] == "KR"):
@ -24,6 +27,7 @@ def getSloReqCountSelector(service_names):
print("Building Service requests count selector for: "+service_names) print("Building Service requests count selector for: "+service_names)
selector = "builtin:service.requestCount.total:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names+")\"))))):splitBy()" selector = "builtin:service.requestCount.total:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names+")\"))))):splitBy()"
return selector return selector
#Generates Response Time selector for given service names
def getSloReqTimeSelector(service_names): def getSloReqTimeSelector(service_names):
selector = "" selector = ""
if(service_names["selectortype"] == "KR"): if(service_names["selectortype"] == "KR"):
@ -31,6 +35,7 @@ def getSloReqTimeSelector(service_names):
elif(service_names["selectortype"] == "SRV"): elif(service_names["selectortype"] == "SRV"):
selector = "builtin:service.response.server:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names["services"]+")\"))))):splitBy()" selector = "builtin:service.response.server:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names["services"]+")\"))))):splitBy()"
return selector return selector
#Parses SLO definition with KRParses and returns service name array
def getSloSrvNames(sloconfig, doc): def getSloSrvNames(sloconfig, doc):
if(sloconfig.hubtype == "gcdm"): if(sloconfig.hubtype == "gcdm"):
krp = krparser.KRParser(name=sloconfig.hub,options=krparser.KROption.RESOLVESERVICES, config={"threads":10, "serviceLookupParams":{"fields":"tags"}, "extendResultObjects":{"env":sloconfig.hub}}, DTAPIURL=sloconfig.remoteurl, DTAPIToken=config(doc[sloconfig.hub][3].get('gcdm-token-name'))) krp = krparser.KRParser(name=sloconfig.hub,options=krparser.KROption.RESOLVESERVICES, config={"threads":10, "serviceLookupParams":{"fields":"tags"}, "extendResultObjects":{"env":sloconfig.hub}}, DTAPIURL=sloconfig.remoteurl, DTAPIToken=config(doc[sloconfig.hub][3].get('gcdm-token-name')))

View File

@ -1,8 +1,11 @@
#Library includes functions for tile generation
from dataExplorerTile import SingleValueTile from dataExplorerTile import SingleValueTile
from dataExplorerTile import GraphTile from dataExplorerTile import GraphTile
from sloHelper import getSloReqTimeSelector from sloHelper import getSloReqTimeSelector
from sloHelper import getSloReqCountSelector from sloHelper import getSloReqCountSelector
from sloHelper import getSloSrvNames from sloHelper import getSloSrvNames
#Load coloring thresholds from strings defined in yaml file
def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor): def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor):
value1 = int(str(sloThresholdValuesAndColor).split("|")[0].split("_")[0]) value1 = int(str(sloThresholdValuesAndColor).split("|")[0].split("_")[0])
@ -16,6 +19,7 @@ def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor):
dataExplorerTileThreshold = [ { "value": value1, "color": color1 }, { "value": value2, "color": color2 }, { "value": value3, "color": color3 } ] dataExplorerTileThreshold = [ { "value": value1, "color": color1 }, { "value": value2, "color": color2 }, { "value": value3, "color": color3 } ]
return dataExplorerTileThreshold return dataExplorerTileThreshold
#Translate row numbers into grid values
def get_bounds (grid_row, grid_column, tile_columnwidth, row_height): def get_bounds (grid_row, grid_column, tile_columnwidth, row_height):
grid_brick = 38 grid_brick = 38
grid_top = 0 if grid_row == 0 else grid_row * grid_brick grid_top = 0 if grid_row == 0 else grid_row * grid_brick
@ -24,6 +28,7 @@ def get_bounds (grid_row, grid_column, tile_columnwidth, row_height):
grod_height = 0 if row_height == 0 else row_height * grid_brick grod_height = 0 if row_height == 0 else row_height * grid_brick
bounds = { "top": grid_top, "left": grod_left, "width": grod_width, "height": grod_height } bounds = { "top": grid_top, "left": grod_left, "width": grod_width, "height": grod_height }
return bounds return bounds
#Generate image tile for dashboard
def createImageTile(config): def createImageTile(config):
newimage = {} newimage = {}
newimage["name"] = "Image" newimage["name"] = "Image"
@ -33,6 +38,7 @@ def createImageTile(config):
newimage["tileFilter"] = {} newimage["tileFilter"] = {}
newimage["image"] = config["visualconfig"]["image_data"] newimage["image"] = config["visualconfig"]["image_data"]
return newimage return newimage
#Generate markdown tile for dashboard
def createMarkdownTile(row, name, markdown, column, width, height): def createMarkdownTile(row, name, markdown, column, width, height):
newmarkdown = {} newmarkdown = {}
newmarkdown["name"] = name newmarkdown["name"] = name
@ -41,6 +47,7 @@ def createMarkdownTile(row, name, markdown, column, width, height):
newmarkdown["bounds"] = get_bounds(row, column, width, height) newmarkdown["bounds"] = get_bounds(row, column, width, height)
newmarkdown["markdown"] = markdown newmarkdown["markdown"] = markdown
return newmarkdown return newmarkdown
#Generate header tile for dashboard
def createHeaderTile(row, name, column, width, height): def createHeaderTile(row, name, column, width, height):
newheader= {} newheader= {}
newheader["name"] = name newheader["name"] = name
@ -48,19 +55,16 @@ def createHeaderTile(row, name, column, width, height):
newheader["configured"] = "true" newheader["configured"] = "true"
newheader["bounds"] = get_bounds(row, column, width, height) newheader["bounds"] = get_bounds(row, column, width, height)
return newheader return newheader
def createSloDescriptionTile(index, name_short, department, config, docURL,slorelevant,slourl_EMEA,slourl_NA,slourl_CN,wall): #TODO #Generate markdown text for SLO description tile
def createSloDescriptionTile(index, name_short, department, config, docURL,slorelevant,slourls,wall): #TODO
if(not wall): if(not wall):
markdown = "___________\n## " + name_short + "\n\n" + department + " [Documentation](" + docURL + ")" markdown = "___________\n## " + name_short + "\n\n" + department + " [Documentation](" + docURL + ")"
if(slorelevant): if(slorelevant):
markdown = markdown + " [QM-Report] \n" markdown = markdown + " [QM-Report] \n"
else: else:
markdown = markdown + " \n" markdown = markdown + " \n"
if(slourl_EMEA): for slourl in slourls:
markdown = markdown + "[EMEA]("+slourl_EMEA+") " markdown = markdown + "["+slourl.upper()+"]("+slourls[slourl]+") "
if(slourl_NA):
markdown = markdown + "[NA]("+slourl_NA+") "
if(slourl_CN):
markdown = markdown + "[CN]("+slourl_CN+") "
else: else:
markdown = "___________\n## " + name_short + "\n\n" + department markdown = "___________\n## " + name_short + "\n\n" + department
if(slorelevant): if(slorelevant):
@ -68,6 +72,7 @@ def createSloDescriptionTile(index, name_short, department, config, docURL,slore
else: else:
markdown = markdown + " \n" markdown = markdown + " \n"
return createMarkdownTile(index*config["visualconfig"]["rowheight"],"Markdown",markdown,0,config["visualconfig"]["description_tile_width"],config["visualconfig"]["rowheight"]) return createMarkdownTile(index*config["visualconfig"]["rowheight"],"Markdown",markdown,0,config["visualconfig"]["description_tile_width"],config["visualconfig"]["rowheight"])
#Generate summed header tiles for all hubs
def createHeaderTiles(config,wall): def createHeaderTiles(config,wall):
tiles = [] tiles = []
if(wall): if(wall):
@ -92,17 +97,19 @@ def createHeaderTiles(config,wall):
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"] currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"]
return tiles return tiles
#Create full SLO row
def createSloTileRow(index, config, wall, sloconfigs,doc): def createSloTileRow(index, config, wall, sloconfigs,doc):
tiles = [] tiles = []
tiles.append(createSloDescriptionTile(index,sloconfigs[0].displayName,sloconfigs[0].department,config,sloconfigs[0].docurl,sloconfigs[0].slorelevant,"","","",wall)) sloUrls = {}
currentColumn = config["visualconfig"]["description_tile_width"] currentColumn = config["visualconfig"]["description_tile_width"]
for sloconfig in sloconfigs: for sloconfig in sloconfigs:
service_names = getSloSrvNames(sloconfig, doc) service_names = getSloSrvNames(sloconfig, doc)
sloUrls[sloconfig.hub] = sloconfig.slourl
if sloconfig.tiles_actual and wall: if sloconfig.tiles_actual and wall:
tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold)
tiles.append(vars(tile)) tiles.append(vars(tile))
elif sloconfig.tiles_actual: elif sloconfig.tiles_actual:
tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold)
tiles.append(vars(tile)) tiles.append(vars(tile))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"] currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
if sloconfig.tiles_graph: if sloconfig.tiles_graph:
@ -115,15 +122,17 @@ def createSloTileRow(index, config, wall, sloconfigs,doc):
else: else:
latency_selector = getSloReqTimeSelector(service_names) latency_selector = getSloReqTimeSelector(service_names)
if wall: if wall:
tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config)
else:
tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width_wall"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config) tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width_wall"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config)
else:
tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config)
tiles.append(vars(tile)) tiles.append(vars(tile))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"] currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
if sloconfig.tiles_ytd and wall: if sloconfig.tiles_ytd and wall:
tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold)
tiles.append(vars(tile))
elif sloconfig.tiles_ytd:
tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold)
tiles.append(vars(tile)) tiles.append(vars(tile))
elif sloconfig.tiles_ytd:
tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold)
tiles.append(vars(tile))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"]
tiles = [createSloDescriptionTile(index,sloconfigs[0].displayName,sloconfigs[0].department,config,sloconfigs[0].docurl,sloconfigs[0].slorelevant,sloUrls,wall)] +tiles
return tiles return tiles