{"version":11,"variables":[{"key":"SnowAppID","type":"csv","input":"APP15096, APP06631, APP07227, APP08313, APP03043, APP00078, APP12126, APP04449, APP07362, APP20974, APP10246, APP10273, APP08174, APP09026, APP06615, APP10045, APP11054, APP02772, APP14997, APP20974, APP06419, APP13038, APP11262, APP03805, APP08981, APP00655, APP05603, APP09553, APP03537, APP20423, APP09955, APP05616, APP14243, APP21002, APP15620, APP20336, APP13483, APP12719, APP20587, APP07701, APP03650, APP13700, APP13849, APP08247, APP02814, APP10955, APP10721, APP06742, APP11939, APP14352, APP11273, APP15584, APP20943, APP12539, APP12516, APP07163, APP07448, APP10084, APP20587, APP11083, APP14196, APP07576, APP12360, APP15619, APP20919, APP06673, APP10613, APP21022, APP12212, APP02677, APP20504, APP01862, APP05671, APP11733, APP12218, APP09702, APP11848, APP15263, APP20524, APP15182, APP07256, APP01883, APP06646, APP07251, APP15256, APP09390, APP09707, APP14192, APP13509, APP02585, APP10385, APP15263, APP11270, APP20775, APP04679, APP04146, APP20924, APP11935, APP03822, APP03032, APP08872, APP14622, APP07570, APP12648, APP02806, APP07399, APP05626, APP11217, APP05524, APP02753, APP07300, APP13292, APP09785, APP10427, APP15269, APP10602, APP11755, APP13619, APP00750, APP15512, APP04689, APP15083, APP00060, APP09707, APP02703, APP03130, APP05559, APP02602, APP10246, APP12350, APP08036, APP01061, APP20712, APP04188, APP13509, APP14729, APP12634, APP10452, APP14625, APP09530, APP13986, APP02701, APP08305, APP20278, APP12569, APP12762, APP14665, APP09312, APP03538, APP08394, APP11314, APP05072, APP14687, APP06819, APP14724, APP10319, APP20323, APP13389, APP20560, APP05242, APP07672, APP10554, APP09578, APP12973, APP09747, APP10261, APP00442, APP05877, APP02422, APP20969, APP02812, APP00279, APP10313, APP09114, APP15100, APP03604, APP06778, APP09843, APP20725, APP06856, APP14391, APP04292, APP13509, APP06635, APP12654, APP10045, APP00842, APP07280, APP09318, APP08318, APP00174, APP09309, APP12254, APP09751, APP14493, APP12014, APP12486, APP11236, APP14958, APP05048, APP11325, APP05604, APP07755, APP05565, APP02515, APP10489, APP08037, APP08190, APP10074, APP06664, APP07269, APP13670, APP10705, APP10430, APP14074, APP02468, APP03055, APP08233, APP03438, APP06677, APP12500, APP05623, APP01191, APP12781, APP01037, APP12499, APP09757, APP10287, APP05143, APP07525, APP11581, APP07743, APP11312, APP02497, APP02311, APP05745, APP08917, APP04046, APP11346, APP08603, APP00472, APP06432, APP15182, APP14617, APP07470, APP12438, APP14923, APP07761, APP13927, APP09048, APP13880, APP15582, APP13413, APP14689, APP20676, APP13048, APP20974, APP03206, APP06672, APP08825, APP06149, APP14683, APP05209, APP04830, APP20780, APP13997, APP02813, APP12241, APP10511, APP11188, APP13383, APP08434, APP21037, APP13153, APP11793, APP13411, APP15549, APP04004, APP13742, APP20974, APP04168, APP06634, APP00470, APP21139, APP13501, APP21300, APP03156, APP13396, APP08415, APP02906, APP06857, APP06633, APP20966, APP00185, APP09037, APP09104, APP11941, APP13287, APP06713, APP07199, APP20960, APP12527, APP12669, APP02552, APP11920, APP05012, APP06716, APP05148, APP20351, APP10958, APP09351, APP11907, APP20940, APP09846, APP05629, APP02699, APP20238, APP09844, APP07291, APP20320, APP03234, APP20180, APP08313, APP05609, APP08193, APP13316, APP05311, APP11610, APP12779, APP20238, APP07762, APP09057, APP10696, APP00266, APP13694, APP02129, APP08398, APP09183, APP03898, APP13695, APP14005, APP04037, APP00202, APP12367, APP00526, APP06046, APP10555, APP02492, APP00855, APP09069, APP00791, APP20584, APP14674, APP15380, APP13926, APP08680, APP20714, APP06464, APP07074, APP14379, APP13367, APP11828, APP09845, APP02466, APP13502, APP02979, APP09310, APP05133, APP00111, APP03847, APP13294, APP04795, APP11054, APP03460, APP07223, APP10230, APP08294, APP13374, APP11316, APP13509, APP04079, APP00596, APP04159, APP05411, APP03802, APP09317, APP04573, APP00742, APP05038, APP05026, APP13879, APP07649, APP14787, APP12966, APP09053, APP10679, APP05573, APP13293, APP10963, APP13824, APP09704, APP14973, APP02121, APP08293, APP01019, APP21262, APP02421, APP03773, APP09028, APP00192, APP13840, APP03775, APP09099, APP13513, APP14688, APP04891, APP15601, APP20582, APP14997, APP13545, APP00816, APP07472, APP02499, APP09301, APP00647, APP12607, APP14507, APP09580, APP13509, APP12633, APP03457, APP10575, APP09062, APP04005, APP10793, APP13791, APP13622, APP07885, APP02508, APP06826, APP08217, APP04877, APP13030, APP09682, APP09644, APP05614, APP07265, APP13625, APP03195, APP09308, APP10824, APP12505, APP14801, APP14408, APP03720","multiple":true}],"tiles":{"4":{"type":"code","title":"Quality Gates Overview","input":"import { monitoredEntitiesClient, settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const tagKey = \"SnowAppID\"; // Amend this to your required tagKey. My Example shows as this in tenant: SnowAppID:APP12345\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const idsFromHosts = [];\n const idsFromAlertingProfiles = [];\n const idsFromSLO = [];\n const idsFromManagementZones = [];\n const idsFromDashboard = [];\n const problemNotifications = {}; // Store Problem Notifications\n\n // First Part - IDs from Hosts\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : { entitySelector: 'type(\"host\")', fields: \"tags\" };\n const res = await monitoredEntitiesClient.getEntities(params);\n nextPageKey = res.nextPageKey;\n if (res.entities) {\n res.entities.forEach(entity => {\n if (entity.tags) {\n entity.tags.forEach(tag => {\n if (tag.key === tagKey && tag.value && snowAppIDs.includes(tag.value) && !idsFromHosts.includes(tag.value)) {\n idsFromHosts.push(tag.value);\n }\n });\n }\n });\n }\n } while (nextPageKey);\n\n // Second part - IDs from Alerting Profiles\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:alerting.profile\",\n fields: \"summary,value\",\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromAlertingProfiles.push({\n appId: appId,\n delayInMinutes: calculateDelayInMinutes(item.value),\n });\n }\n })\n }\n } while (nextPageKey);\n\n // Third part - IDs from Management Zones\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:management-zones\",\n fields: \"summary\",\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromManagementZones.push(appId);\n }\n })\n }\n } while (nextPageKey);\n\n // Fourth part - IDs from Problem Notifications\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:problem.notifications\",\n fields: \"summary,value\", // Include the \"enabled\" field\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n const isEnabled = item.value.enabled === true; // Check if \"enabled\" is true (boolean)\n problemNotifications[appId] = isEnabled;\n }\n })\n }\n } while (nextPageKey);\n\n // Fifth part - IDs from Dashboards\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:dashboards.general\",\n fields: \"summary\",\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromDashboard.push(appId);\n }\n })\n }\n } while (nextPageKey);\n\n // Fifth part - IDs from SLO\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:monitoring.slo\",\n fields: \"summary\",\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromSLO.push({\n appId: appId,\n });\n }\n })\n }\n } while (nextPageKey);\n\n\n\n // Function to calculate delayInMinutes for an alerting profile\n function calculateDelayInMinutes(alertingProfile) {\n if (!alertingProfile || !alertingProfile.severityRules) {\n return 0;\n }\n return alertingProfile.severityRules.reduce((totalDelay, rule) => {\n return totalDelay + (rule.delayInMinutes || 0);\n }, 0);\n }\n\n // Add the new columns \"ProblemNotification\" and \"ProblemNotificationsEnabled\" to the result array\n const result = snowAppIDs.map(id => {\n const alertingProfile = idsFromAlertingProfiles.find(profile => profile.appId === id);\n const alertingProfileConfigured = alertingProfile ? alertingProfile.delayInMinutes : 0;\n\n // Determine if Problem Notification exists for this ID\n const isProblemNotification = problemNotifications[id] !== undefined;\n\n // Determine if Problem Notification is enabled or disabled for this ID\n const isProblemNotificationEnabled = isProblemNotification && problemNotifications[id] === true;\n\n // Calculate the Quality as the percentage of green configurations to total configurations\n const totalApplicationConfigurations = 5; // Assuming 5 possible configurations including Problem Notification\n\n const greenConfigurationsCount = [\n idsFromAlertingProfiles.some(profile => profile.appId === id),\n idsFromSLO.includes(id),\n idsFromDashboard.includes(id),\n idsFromManagementZones.includes(id),\n isProblemNotification,\n isProblemNotificationEnabled,\n alertingProfileConfigured > 0, // Check if alerting profile is configured\n ].filter(value => value).length;\n\n const percentageGreen = (greenConfigurationsCount / totalApplicationConfigurations) * 100;\n\n return {\n ApplicationID: id,\n SLO: idsFromSLO.includes(id) ? \"🟢\" : \"🔴\",\n Dashboard: idsFromDashboard.includes(id) ? \"🟢\" : \"🔴\",\n ManagementZones: idsFromManagementZones.includes(id) ? \"🟢\" : \"🔴\",\n AlertingProfile: idsFromAlertingProfiles.some(profile => profile.appId === id) ? \"🟢\" : \"🔴\",\n AlertingProfileConfigured: alertingProfileConfigured === 0 ? \"🔴\" : \"🟢\",\n ProblemNotification: isProblemNotification ? \"🟢\" : \"🔴\",\n ProblemNotificationsEnabled: isProblemNotificationEnabled ? \"🟢\" : \"🔴\", // Green if true, red if false\n Quality: `${percentageGreen.toFixed(2)}%`,\n };\n });\n\n // Sort the result by Quality in ascending order\n result.sort((a, b) => parseFloat(a.Quality) - parseFloat(b.Quality));\n\n return result;\n}\n","visualization":"table","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":true,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"11":{"type":"code","title":"Alerting Profiles Configured","input":"import { settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const alertingProfiles = await getAllAlertingProfiles();\n\n const bulbStatusMap = new Map();\n\n // Process each specified SnowAppID\n snowAppIDs.forEach(snowAppID => {\n const alertingProfile = alertingProfiles.find(profile => {\n const nameMatch = /(APP\\d{5})/.exec(profile.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n return nameMatch[1] === snowAppID;\n }\n return false;\n });\n\n if (alertingProfile) {\n const delayInMinutes = calculateDelayInMinutes(alertingProfile.value);\n // Determine bulb status (red or green)\n const bulbStatus = delayInMinutes === 0 ? 'Red' : 'Green';\n bulbStatusMap.set(snowAppID, bulbStatus);\n }\n });\n\n // Calculate the percentage of green bulbs\n const totalSnowAppIDs = snowAppIDs.length;\n const greenBulbsCount = [...bulbStatusMap.values()].filter(status => status === 'Green').length;\n const percentageGreen = (greenBulbsCount / totalSnowAppIDs) * 100 || 0;\n\n return percentageGreen.toFixed(2) + \"%\";\n}\n\n// Function to get all Alerting Profiles\nasync function getAllAlertingProfiles() {\n let nextPageKey;\n const alertingProfiles = [];\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:alerting.profile\",\n fields: \"summary,value\",\n };\n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n alertingProfiles.push(...res.items);\n }\n } while (nextPageKey);\n\n return alertingProfiles;\n}\n\n// Function to calculate delayInMinutes for an alerting profile\nfunction calculateDelayInMinutes(alertingProfile) {\n if (!alertingProfile || !alertingProfile.severityRules) {\n return 0;\n }\n return alertingProfile.severityRules.reduce((totalDelay, rule) => {\n return totalDelay + (rule.delayInMinutes || 0);\n }, 0);\n}\n","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":false,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"17":{"type":"markdown","title":"","content":"# Quality Gates 🚦\nBest Practice Configuration"},"21":{"type":"markdown","title":"","content":"# 📖 Helpful Documentation \n### Onboarding to Dynatrace\n[Onboarding to Dynatrace](https://www.collab.bt.com/confluence/display/DYNAWIKI/0.+Onboarding+to+Dynatrace)\n\n[Reduce Alert Noise](https://www.collab.bt.com/confluence/display/DYNAWIKI/2.+Reducing+Alert+Noise)\n\n[Alerting Framework](https://www.collab.bt.com/confluence/display/DYNAWIKI/1.+Alerting+Framework+Overview)\n\n[Dynatrace Problems Overview](https://www.collab.bt.com/confluence/display/DYNAWIKI/Dynatrace+Problems+Overview)\n\n[Dynatrace Naming Conventions](https://www.collab.bt.com/confluence/display/DYNAWIKI/Dynatrace+Naming+Conventions)\n\n[Service-Level Objetives](https://www.collab.bt.com/confluence/display/DYNAWIKI/Service-Level+Objetives)\n\n[ServiceNow Integration](https://www.collab.bt.com/confluence/display/DYNAWIKI/Dynatrace-ServiceNow+Integration)\n\n### Support\n[Services and Support Model](https://www.collab.bt.com/confluence/display/DYNAWIKI/09-+Services+and+Support+Model)\n\n"},"22":{"type":"code","title":"Management Zones","input":"import { monitoredEntitiesClient, settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const tagKey = \"SnowAppID\"; // Amend this to your required tagKey. My Example shows as this in tenant: SnowAppID:APP12345\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const idsFromHosts = [];\n const idsFromManagementZones = [];\n\n // First Part - IDs from Hosts\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : { entitySelector: 'type(\"host\")', fields: \"tags\" };\n const res = await monitoredEntitiesClient.getEntities(params);\n nextPageKey = res.nextPageKey;\n if (res.entities) {\n res.entities.forEach(entity => {\n if (entity.tags) {\n entity.tags.forEach(tag => {\n if (tag.key === tagKey && tag.value && snowAppIDs.includes(tag.value) && !idsFromHosts.includes(tag.value)) {\n idsFromHosts.push(tag.value);\n }\n });\n }\n });\n }\n } while (nextPageKey);\n\n // Second part - IDs from Management Zones\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:management-zones\",\n fields: \"summary\",\n };\n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromManagementZones.push(appId);\n }\n });\n }\n } while (nextPageKey);\n\n // Calculate percentage of specified SnowAppIDs with matching Management Zones\n const matchingIdsCount = idsFromHosts.filter(id => idsFromManagementZones.includes(id)).length;\n const totalIdsCount = idsFromHosts.length;\n const percentageMatch = (matchingIdsCount / totalIdsCount) * 100 || 0;\n\n return percentageMatch.toFixed(2) + \"%\";\n}\n","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":false,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"23":{"type":"code","title":"Alerting Profiles","input":"import { monitoredEntitiesClient, settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const tagKey = \"SnowAppID\"; // Amend this to your required tagKey. My Example shows as this in tenant: SnowAppID:APP12345\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const idsFromHosts = [];\n const idsFromAlertingProfiles = [];\n\n // First Part - IDs from Hosts\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : { entitySelector: 'type(\"host\")', fields: \"tags\" };\n const res = await monitoredEntitiesClient.getEntities(params);\n nextPageKey = res.nextPageKey;\n if (res.entities) {\n res.entities.forEach(entity => {\n if (entity.tags) {\n entity.tags.forEach(tag => {\n if (tag.key === tagKey && tag.value && snowAppIDs.includes(tag.value) && !idsFromHosts.includes(tag.value)) {\n idsFromHosts.push(tag.value);\n }\n });\n }\n });\n }\n } while (nextPageKey);\n\n // Second part - IDs from Alerting Profiles\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:alerting.profile\",\n fields: \"summary,value\",\n };\n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n idsFromAlertingProfiles.push(appId);\n }\n });\n }\n } while (nextPageKey);\n\n // Calculate percentage of specified SnowAppIDs with matching Alerting Profiles\n const matchingIdsCount = idsFromHosts.filter(id => idsFromAlertingProfiles.includes(id)).length;\n const totalIdsCount = idsFromHosts.length;\n const percentageMatch = (matchingIdsCount / totalIdsCount) * 100 || 0;\n\n return percentageMatch.toFixed(2) + \"%\";\n}\n","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":false,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"24":{"type":"code","title":"Problem Notifications","input":"import { monitoredEntitiesClient, settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const tagKey = \"SnowAppID\"; // Amend this to your required tagKey. My Example shows as this in tenant: SnowAppID:APP12345\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const idsFromHosts = [];\n const problemNotifications = {};\n const problemNotificationsExistMap = new Map();\n\n // First Part - IDs from Hosts\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : { entitySelector: 'type(\"host\")', fields: \"tags\" };\n const res = await monitoredEntitiesClient.getEntities(params);\n nextPageKey = res.nextPageKey;\n if (res.entities) {\n res.entities.forEach(entity => {\n if (entity.tags) {\n entity.tags.forEach(tag => {\n if (tag.key === tagKey && tag.value && snowAppIDs.includes(tag.value) && !idsFromHosts.includes(tag.value)) {\n idsFromHosts.push(tag.value);\n }\n });\n }\n });\n }\n } while (nextPageKey);\n\n // Fourth part - IDs from Problem Notifications\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:problem.notifications\",\n fields: \"summary,value\", // Include the \"enabled\" field\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n problemNotifications[appId] = true;\n problemNotificationsExistMap.set(appId, true);\n }\n });\n }\n } while (nextPageKey);\n\n // Calculate percentage of specified SnowAppIDs with matching Problem Notifications that exist\n const matchingIdsCount = idsFromHosts.filter(id => problemNotifications[id] === true).length;\n const totalIdsCount = idsFromHosts.length;\n const percentageExist = (matchingIdsCount / totalIdsCount) * 100 || 0;\n\n return {\n PercentageExist: percentageExist.toFixed(2) + \"%\",\n ProblemNotificationsExistMap: problemNotificationsExistMap,\n };\n}\n","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":false,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"25":{"type":"code","title":"Problem Notifications Configured","input":"import { monitoredEntitiesClient, settingsObjectsClient } from '@dynatrace-sdk/client-classic-environment-v2';\n\nexport default async function () {\n const tagKey = \"SnowAppID\"; // Amend this to your required tagKey. My Example shows as this in tenant: SnowAppID:APP12345\n const snowAppIDs = Array.isArray($SnowAppID) ? $SnowAppID : [$SnowAppID]; // Convert to an array if it's not already\n\n let nextPageKey;\n const idsFromHosts = [];\n const problemNotifications = {};\n const problemNotificationsEnabledMap = new Map();\n\n // First Part - IDs from Hosts\n\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : { entitySelector: 'type(\"host\")', fields: \"tags\" };\n const res = await monitoredEntitiesClient.getEntities(params);\n nextPageKey = res.nextPageKey;\n if (res.entities) {\n res.entities.forEach(entity => {\n if (entity.tags) {\n entity.tags.forEach(tag => {\n if (tag.key === tagKey && tag.value && snowAppIDs.includes(tag.value) && !idsFromHosts.includes(tag.value)) {\n idsFromHosts.push(tag.value);\n }\n });\n }\n });\n }\n } while (nextPageKey);\n\n // Fourth part - IDs from Problem Notifications\n\n nextPageKey = undefined;\n do {\n const params = nextPageKey ? { nextPageKey: nextPageKey } : {\n schemaIds: \"builtin:problem.notifications\",\n fields: \"summary,value\", // Include the \"enabled\" field\n } \n const res = await settingsObjectsClient.getSettingsObjects(params);\n nextPageKey = res.nextPageKey;\n if (res.items) {\n res.items.forEach(item => {\n let appId;\n const nameMatch = /(APP\\d{5})/.exec(item.summary ?? \"\");\n if (nameMatch && nameMatch.length > 1) {\n appId = nameMatch[1];\n }\n if (appId && snowAppIDs.includes(appId)) {\n const isEnabled = item.value.enabled === true;\n problemNotifications[appId] = isEnabled;\n problemNotificationsEnabledMap.set(appId, isEnabled);\n }\n });\n }\n } while (nextPageKey);\n\n // Calculate percentage of specified SnowAppIDs with matching Problem Notifications that are enabled\n const matchingIdsCount = idsFromHosts.filter(id => problemNotifications[id] === true).length;\n const totalIdsCount = idsFromHosts.length;\n const percentageMatch = (matchingIdsCount / totalIdsCount) * 100 || 0;\n\n return {\n PercentageMatch: percentageMatch.toFixed(2) + \"%\",\n ProblemNotificationsEnabledMap: problemNotificationsEnabledMap,\n };\n}\n","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect"},"singleValue":{"showLabel":false,"label":"","autoscale":true},"table":{"enableLineWrap":true,"hiddenColumns":[],"lineWrapIds":[["AlertingProfile"]]}}},"26":{"type":"data","title":"Problem Count Per SnowAppID - Last 7 Days","query":"fetch events, from:now()-7d\n| filter event.kind == \"DAVIS_PROBLEM\"\n| fieldsAdd Tags = toString(entity_tags)\n| expand Tags\n| parse Tags, \"DATA '\\\"SnowAppID:' ALNUM{8}:SnowAppID\"\n| summarize ProblemCount = countDistinct(display_id),by:{SnowAppID}\n| filter in(SnowAppID, array($SnowAppID))\n| sort ProblemCount desc","visualization":"table","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"absolute"},"categoricalBarChartSettings":{"categoryAxis":"SnowAppID","categoryAxisLabel":"SnowAppID","valueAxis":"ProblemCount","valueAxisLabel":"ProblemCount"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center"},"table":{"rowDensity":"condensed","enableLineWrap":true,"enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}}}},"27":{"type":"data","title":"Problem Count Per SnowAppID by Severity - Last 7 Days","query":"fetch events, from:now()-7d\n| filter event.kind == \"DAVIS_PROBLEM\"\n| fieldsAdd Tags = toString(entity_tags)\n| expand Tags\n| parse Tags, \"DATA '\\\"SnowAppID:' ALNUM{8}:SnowAppID\"\n| summarize ProblemCount = countDistinct(display_id),by:{SnowAppID, EventCategory = event.category, EventName = event.name}\n| filter in(SnowAppID, array($SnowAppID))\n| sort ProblemCount desc\n| sort ProblemCount desc\n| sort EventCategory\n| sort EventName\n| sort EventCategory desc","visualization":"table","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"absolute"},"categoricalBarChartSettings":{"categoryAxis":"SnowAppID","categoryAxisLabel":"SnowAppID","valueAxis":"ProblemCount","valueAxisLabel":"ProblemCount"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center"},"table":{"rowDensity":"condensed","enableLineWrap":true,"enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}}}},"28":{"type":"data","title":"Average Problem Duration Per SnowAppID by Severity - Last 7 Days","query":"fetch events, from:now()-7d\n| filter event.kind == \"DAVIS_PROBLEM\"\n| fieldsAdd Tags = toString(entity_tags)\n| expand Tags\n| parse Tags, \"DATA '\\\"SnowAppID:' ALNUM{8}:SnowAppID\"\n| filter isNotNull(resolved_problem_duration)\n| filter event.status == \"CLOSED\"\n| summarize AverageProblem = avg(resolved_problem_duration),by:{SnowAppID, EventCategory = event.category, EventName = event.name}\n//| fieldsAdd ProblemCount = countDistinct(display_id)\n| filter in(SnowAppID, array($SnowAppID))\n| sort AverageProblem asc\n","visualization":"table","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"absolute"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center"},"table":{"rowDensity":"condensed","enableLineWrap":true,"enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}}}},"29":{"type":"markdown","title":"","content":"# Problem Overview 🚨"}},"layouts":{"4":{"x":5,"y":0,"w":13,"h":15},"11":{"x":0,"y":6,"w":5,"h":2},"17":{"x":0,"y":0,"w":5,"h":2},"21":{"x":18,"y":0,"w":6,"h":14},"22":{"x":0,"y":2,"w":5,"h":2},"23":{"x":0,"y":4,"w":5,"h":2},"24":{"x":0,"y":8,"w":5,"h":2},"25":{"x":0,"y":10,"w":5,"h":2},"26":{"x":0,"y":16,"w":6,"h":8},"27":{"x":6,"y":16,"w":8,"h":8},"28":{"x":14,"y":16,"w":10,"h":8},"29":{"x":0,"y":15,"w":24,"h":1}}}