diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 03002ab..8b5bd7d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -19,6 +19,7 @@ on:
branches: [ master ]
schedule:
- cron: "0 0 * * *"
+ workflow_dispatch:
jobs:
analyze:
@@ -67,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 7a870c4..0e1a8bd 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -1,5 +1,9 @@
name: format with black
-on: [ push, pull_request ]
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
jobs:
linter_name:
name: runner / black
@@ -7,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Validate with Black
- uses: rickstaa/action-black@v1
+ uses: psf/black@stable
id: action_black
with:
black_args: ". --line-length=150"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 06cf143..f933657 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -9,6 +9,8 @@ on:
schedule:
# run tests every night at midnight
- cron: "0 0 * * *"
+ workflow_dispatch:
+
jobs:
build:
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index dfd893a..2e93b01 100644
--- a/README.md
+++ b/README.md
@@ -173,20 +173,28 @@ This program will create the following files in the `out` directory.
## Program Architecture
### General Description
-- Configuration Assessment Tool or config-assessment-tool as it is referenced on GitHub is an open-source project that was developed by AppDynamics Engineers with the purpose of evaluating the configuration and quality of the instrumentation of applications that are being monitored by the AppDynamics Application Performance Monitoring (APM) product suite. Its intended audience are AppDynamics/Cisco customers as well as AppD/Cisco personal that assist the customers in improving the instrumentation of their applications. The tool is Python (3.9+) based and hence requires a Python installation.
-- Users run the tool directly from source, or using docker container (locally build only and not pulled from any repo) or the executable bundle (Windows and Linux bundles) that contains shared libraries or executable files for Python. If users wish to run the code using Docker, they must build to the local image on their platform(using the provided Dockerfile), therefore a Docker engine install is also required if running from Docker containers. Refer to the GitHub repository for instructions on how to build the image and run the tool using Docker.
-- There are Python packages that are required that are pulled from Pypi package repository when config-assessment-tool is installed. These can be examined by following the build from source instructions outlined on the GitHub repo.
-When config-assessment-tool starts it reads the job file with customer provided properties and connects to the AppDynamics controller url as defined in that job file. The controller(s) can be an AppDynamics hosted Saas controller(s) or customer's on-premises installation(s). The tool also uses the credentials in the job file to authenticate to the controller and using the generatee temporary session token, it uses the AppDynamics Controller REST API’s to pull various metrics and generate the “output” directory that contains these metrics in the form of various Excel Worksheet files along with some other supplemental data files. These are the artifacts that are used by the customers to examine and provide various metrics around how well each of the applications that are being monitored on the respective controller.
-- There are no other communication from the tool to any other external services. We solely utilize the AppDynamics’s Controller REST API’s. See the online API documentation for the superset reference for more information.
-- Consult the below links for the aforementioned references:
- - AppDynamics API's: https://docs.appdynamics.com/appd/22.x/latest/en/extend-appdynamics/appdynamics-apis#AppDynamicsAPIs-apiindex
- - Job file: https://github.com/Appdynamics/config-assessment-tool/blob/master/input/jobs/DefaultJob.json
- - Buil from source: https://github.com/Appdynamics/config-assessment-tool#from-source
- - config-assessment-tool GitHub open source project: https://github.com/Appdynamics/config-assessment-tool
- - AppDynamics - https://www.appdynamics.com/
- - Docker: https://docs.docker.com/
- - PyPi Python package repo: https://pypi.org/
+The Configuration Assessment Tool, also known as config-assessment-tool on GitHub, is an open-source project developed by AppDynamics engineers. Its purpose is to evaluate the configuration and quality of instrumentation in applications that are monitored by the AppDynamics Application Performance Monitoring (APM) product suite. The intended audience for this tool is AppDynamics/Cisco customers and AppD/Cisco personnel who assist customers in improving the instrumentation of their applications. The tool is Python-based (3.9+) and therefore requires a Python installation unless using the self-contained platform specific executable bundles(Linux,Windows).
+Users can run the tool directly from the source or use the docker container (locally built only and not pulled from any repo), or the executable bundle (Windows and Linux bundles) that contain shared libraries or executable files for Python. If users wish to run the code using Docker, they must build the local image on their platform (using the provided Dockerfile) as we do not currently publish platform specific Docker images of the tool into any repositories. Therefore, a Docker engine install is also required for container-based install/build and execution.
+
+If users do not wish to install Python and Docker and are looking for a self-contained executable bundle, we recommend using the latest version of the linux tar ball or the Windows zip file available on the release page and follow instructions for [Platform executable installation steps](https://github.com/Appdynamics/config-assessment-tool#platform-executable).
+
+There are Python packages and library dependencies that are required and pulled from the PyPi package repository when config-assessment-tool is installed. These can be examined by following the [build from source instructions](https://github.com/Appdynamics/config-assessment-tool#from-source) and examining the downloaded packages into your local Python environment.
+
+When config-assessment-tool starts, it reads the job file with the customer-provided properties and connects to the AppDynamics controller URL defined in that job file. The controller(s) can be AppDynamics hosted Saas controllers or customer's on-premises installations. The tool also uses the credentials in the job file to authenticate to the controller. Using the generated temporary session token, it uses the AppDynamics Controller REST API to pull various metrics and generate the "output" directory. This directory contains these metrics in the form of various Excel Worksheet files, along with some other supplemental data files. These are the artifacts used by customers to examine and provide various metrics around how well each of the applications being monitored on the respective controller is performing.
+
+There is no other communication from the tool to any other external services. We solely utilize the AppDynamics Controller REST API. See the online API documentation for the superset reference for more information.
+
+Consult the links below for the aforementioned references:
+
+- AppDynamics API's: https://docs.appdynamics.com/appd/22.x/latest/en/extend-appdynamics/appdynamics-apis#AppDynamicsAPIs-apiindex
+- Platform executable bundles: https://github.com/Appdynamics/config-assessment-tool/releases
+- Job file: https://github.com/Appdynamics/config-assessment-tool/blob/master/input/jobs/DefaultJob.json
+- Build from source: https://github.com/Appdynamics/config-assessment-tool#from-source
+- config-assessment-tool GitHub open source project: https://github.com/Appdynamics/config-assessment-tool
+- AppDynamics: https://www.appdynamics.com/
+- Docker: https://docs.docker.com/
+- PyPi Python package repository: https://pypi.org/
### Backend
diff --git a/VERSION b/VERSION
index f2f5e08..a3f6cc5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v1.5.9.4
\ No newline at end of file
+v1.5.9.5
\ No newline at end of file
diff --git a/backend/api/appd/AppDController.py b/backend/api/appd/AppDController.py
index 4e62908..93c0e77 100644
--- a/backend/api/appd/AppDController.py
+++ b/backend/api/appd/AppDController.py
@@ -349,3 +349,8 @@ def getSyntheticPrivateAgentUtilization(self, applicationId: Path, body: Body):
@post("/controller/restui/eumSyntheticJobListUiService/getSessionData")
def getSyntheticSessionData(self, body: Body):
"""Retrieves Synthetic Session Data"""
+
+ @params({"output": "json"})
+ @get("/controller/restui/report/list")
+ def getReportList(self):
+ """Retrieves Report Data"""
diff --git a/backend/config-assessment-tool.spec b/backend/config-assessment-tool.spec
index 2ed3297..9215b8a 100644
--- a/backend/config-assessment-tool.spec
+++ b/backend/config-assessment-tool.spec
@@ -35,6 +35,10 @@ a = Analysis(
("../backend/resources/pptAssets/background_2.jpg", "backend/resources/pptAssets"),
("../backend/resources/pptAssets/criteria.png", "backend/resources/pptAssets"),
("../backend/resources/pptAssets/criteria2.png", "backend/resources/pptAssets"),
+ ("../backend/resources/pptAssets/checkmark.png", "backend/resources/pptAssets"),
+ ("../backend/resources/pptAssets/xmark.png", "backend/resources/pptAssets"),
+ ("../backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json", "backend/resources/pptAssets"),
+ ("../backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx", "backend/resources/pptAssets"),
(path.join(site_packages,"pptx","templates"), "pptx/templates"), # for pptx
],
hiddenimports=[],
@@ -80,3 +84,7 @@ shutil.copyfile("backend/resources/pptAssets/background.jpg", f"{DISTPATH}/{bund
shutil.copyfile("backend/resources/pptAssets/background_2.jpg", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/background_2.jpg")
shutil.copyfile("backend/resources/pptAssets/criteria.png", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/criteria.png")
shutil.copyfile("backend/resources/pptAssets/criteria2.png", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/criteria2.png")
+shutil.copyfile("backend/resources/pptAssets/checkmark.png", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/checkmark.png")
+shutil.copyfile("backend/resources/pptAssets/xmark.png", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/xmark.png")
+shutil.copyfile("backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json")
+shutil.copyfile("backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx", f"{DISTPATH}/{bundle_name}/backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx")
diff --git a/backend/core/Engine.py b/backend/core/Engine.py
index 0f00be7..f096e2e 100644
--- a/backend/core/Engine.py
+++ b/backend/core/Engine.py
@@ -31,6 +31,7 @@
from extractionSteps.maturityAssessment.mrum.NetworkRequestsMRUM import NetworkRequestsMRUM
from extractionSteps.maturityAssessment.mrum.OverallAssessmentMRUM import OverallAssessmentMRUM
from output.presentations.cxPpt import createCxPpt
+from output.presentations.cxPptFsoUseCases import createCxHamUseCasePpt
from output.reports.AgentMatrixReport import AgentMatrixReport
from output.reports.CustomMetricsReport import CustomMetricsReport
from output.reports.DashboardReport import DashboardReport
@@ -358,6 +359,7 @@ def finalize(self, startTime):
)
createCxPpt(self.jobFileName)
+ createCxHamUseCasePpt(self.jobFileName)
logging.info(f"----------Complete----------")
# if controllerData.json file exists, delete it
diff --git a/backend/output/presentations/cxPptFsoUseCases.py b/backend/output/presentations/cxPptFsoUseCases.py
new file mode 100644
index 0000000..c3bad98
--- /dev/null
+++ b/backend/output/presentations/cxPptFsoUseCases.py
@@ -0,0 +1,555 @@
+import json
+import logging
+import os
+import re
+from enum import Enum
+from typing import Dict, Optional
+
+from openpyxl import load_workbook
+from pptx import Presentation
+from pptx.dml.color import RGBColor
+from pptx.oxml.xmlchemy import OxmlElement
+from pptx.util import Pt, Inches
+
+
+class UseCase(tuple):
+ def __init__(self, file: str):
+
+ self.task_id_to_slide = {}
+ self.task_id_to_health_check_status = {}
+
+ with open(file, 'r') as file:
+ data = json.load(file)
+ self.pitstop_data = data['pitstop']
+ self._initSlideMapping()
+
+ def pitstop_data(self) -> Dict:
+ return self.pitstop_data
+
+ def __str__(self) -> str:
+ return self.pitstop_data.values()
+
+ def _initSlideMapping(self):
+ self.setSlideId('INTRO', 0) # Welcome slide page 1
+ self.setSlideId('RACETRACK', 1) # Welcome slide page 2
+ self.setSlideId("onboard", 2)
+ self.setSlideId("onboard_remediation", 3)
+ self.setSlideId("implement", 4)
+ self.setSlideId("implement_remediation", 5)
+ self.setSlideId("use", 6)
+ self.setSlideId("use_remediation", 7)
+ self.setSlideId("engage", 8)
+ self.setSlideId("engage_remediation", 9)
+ self.setSlideId("adopt", 10)
+ self.setSlideId("adopt_remediation", 11)
+ self.setSlideId("optimize", 12)
+ self.setSlideId("optimize_remediation", 13)
+ self.setSlideId("FINAL", 14)
+
+ def getAllSlideIndexes(self):
+ return list(self.task_id_to_slide.values())
+
+ def getAllHealthCheckValues(self):
+ return list(self.task_id_to_health_check_status.values())
+
+ def isFailed(self, pitstop: str) -> bool:
+ tasks = self.getPitstopTasks(pitstop)
+ return any('fail' in self.task_id_to_health_check_status[task].lower() for task in tasks if task in self.task_id_to_health_check_status)
+
+ def pitStopContainsFailureOrManualCheck(self, pitstop: str) -> bool:
+ tasks = self.getPitstopTasks(pitstop)
+ for task in tasks:
+ if task in self.task_id_to_health_check_status \
+ and \
+ 'fail' in self.task_id_to_health_check_status[task].lower() \
+ or \
+ 'manual check' in self.task_id_to_health_check_status[task].lower():
+ return True
+
+ return False
+
+ def _get_task_data(self, task_id: str) -> Dict[str, Optional[str]]:
+ for pitstop_stage in self.pitstop_data:
+ for sequence_number, task_data in self.pitstop_data[pitstop_stage]['checklist_sequence'].items():
+ if task_data['task_id'] == task_id:
+ return {
+ 'checklist_item': task_data['checklist_item'],
+ 'tooltip': task_data.get('tooltip', None),
+ 'exit_criteria_logic': task_data.get('exit_criteria_logic', None),
+ 'min_pass_threshold': task_data.get('min_pass_threshold', None),
+ 'remediation_items': task_data.get('remediation_steps', None)
+ }
+ return {}
+
+ def getAllTaskIds(self):
+ task_ids = []
+ for pitstop_stage in self.pitstop_data:
+ for sequence_number, task_data in self.pitstop_data[pitstop_stage]['checklist_sequence'].items():
+ task_ids.append(task_data['task_id'])
+ return task_ids
+
+ def getPitstopTasks(self, pitstop: str) -> Dict[str, str]:
+ task_ids = []
+ for pitstop_stage in self.pitstop_data[pitstop].values():
+ for k in pitstop_stage.values():
+ task_ids.append(k['task_id'])
+ return task_ids if task_ids else None
+
+ def getChecklistItem(self, task_id: str) -> Optional[str]:
+ task_data = self._get_task_data(task_id)
+ return task_data['checklist_item'] if task_data else None
+
+ def getToolTip(self, task_id: str) -> Optional[str]:
+ task_data = self._get_task_data(task_id)
+ return task_data.get('tooltip', None) if task_data else None
+
+ def getExitCriteriaLogic(self, task_id: str) -> Optional[str]:
+ task_data = self._get_task_data(task_id)
+ return task_data.get('exit_criteria_logic', None) if task_data else None
+
+ def getMinPassThreshold(self, task_id: str) -> Optional[str]:
+ task_data = self._get_task_data(task_id)
+ return task_data.get('min_pass_threshold', None) if task_data else None
+
+ def getRemediationList(self, task_id: str) -> Optional[list]:
+ task_data = self._get_task_data(task_id)
+ return task_data.get('remediation_items', None) if task_data else None
+
+ def setSlideId(self, task_id: str, slide_index: int):
+ self.task_id_to_slide[task_id] = slide_index
+
+ def getSlideId(self, task_id: str) -> Optional[int]:
+ # this avoids a potential KeyError
+ return self.task_id_to_slide.get(task_id, None)
+
+ def setHealthCheckStatus(self, task_id: str, hc: str):
+ self.task_id_to_health_check_status[task_id] = hc
+
+ def getHealthCheckStatus(self, task_id: str) -> Optional[str]:
+ # this avoids a potential KeyError
+ return self.task_id_to_health_check_status.get(task_id, None)
+
+
+class Color(Enum):
+ WHITE = RGBColor(255, 255, 255)
+ BLACK = RGBColor(0, 0, 0)
+ RED = RGBColor(255, 0, 0)
+ GREEN = RGBColor(0, 255, 0)
+ BLUE = RGBColor(0, 0, 255)
+
+
+def addTable(slide, data, color: Color = Color.BLACK, fontSize: int = 16, left: int = 0.25, top: int = 3.5, width: int = 9.5, height: int = 1.5):
+ shape = slide.shapes.add_table(len(data), len(data[0]), Inches(left), Inches(top), Inches(width), Inches(height))
+ table = shape.table
+
+ # Set fixed row height
+ for row_index, row in enumerate(table.rows):
+ if row_index != 0: # Skip the title row
+ row.height = Inches(1)
+
+ pass_mark = search("checkmark.png", "../")
+ fail_mark = search("xmark.png", "../")
+
+ for i, row in enumerate(data):
+ for j, cell in enumerate(row):
+ cell_obj = table.cell(i, j)
+ cell_obj.text = str(cell)
+
+ image_left = Inches(left+2) + table.columns[j].width * j
+ image_top = Inches(top) + table.rows[i].height * i
+ # add marker image
+ if "pass" in str(cell).lower() or "manual check" in str(cell).lower():
+ slide.shapes.add_picture(pass_mark, image_left, image_top, width=Inches(0.2), height=Inches(0.2))
+ if "fail" in str(cell).lower():
+ slide.shapes.add_picture(fail_mark, image_left, image_top, width=Inches(0.2), height=Inches(0.2))
+
+ for paragraph in cell_obj.text_frame.paragraphs:
+ for run in paragraph.runs:
+ run.font.size = Pt(fontSize)
+ run.font.color.rgb = color.value
+
+def addCell(cell, text, color=None, fontSize=16):
+ cell.text = text
+ for paragraph in cell.text_frame.paragraphs:
+ for run in paragraph.runs:
+ run.font.size = Pt(fontSize)
+ if color:
+ run.font.color.rgb = color.value
+
+
+def SubElement(parent, tagname, **kwargs):
+ element = OxmlElement(tagname)
+ element.attrib.update(kwargs)
+ parent.append(element)
+ return element
+
+
+def makeParaBulletPointed(para):
+ """Bullets are set to Arial,
+ actual text can be a different font"""
+ pPr = para._p.get_or_add_pPr()
+ ## Set marL and indent attributes
+ pPr.set('marL', '171450')
+ pPr.set('indent', '171450')
+ ## Add buFont
+ _ = SubElement(parent=pPr,
+ tagname="a:buFont",
+ typeface="Arial",
+ panose="020B0604020202020204",
+ pitchFamily="34",
+ charset="0"
+ )
+ ## Add buChar
+ _ = SubElement(parent=pPr,
+ tagname='a:buChar',
+ char="•")
+
+
+def addHyperLinkCell(run, cell_text):
+ hyperlink_pattern = re.compile(r'(.*?)<\/a>')
+ parts = hyperlink_pattern.split(cell_text)
+ for idx, part in enumerate(parts):
+ if idx % 3 == 0:
+ run.text += part
+ elif idx % 3 == 1:
+ hyperlink_url = part
+ else:
+ run.text += part
+ hlink = run.hyperlink
+ hlink.address = hyperlink_url
+
+
+def addRemediationTable(slide, data, color=None, fontSize=16, left=1.5, top=3.5, width=9.5, height=1.5):
+ shape = slide.shapes.add_table(len(data), len(data[0]), Inches(left), Inches(top), Inches(width), Inches(height))
+ table = shape.table
+ table.columns[0].width = Inches(1.5)
+ table.columns[2].width = Inches(2)
+ table.columns[2].width = Inches(7)
+ hyperlink_pattern = re.compile(r'(.*?)<\/a>')
+
+ for i, row in enumerate(data):
+ for j, cell in enumerate(row):
+ cell_text = str(cell)
+ text_frame = table.cell(i, j).text_frame
+ # Clear any existing paragraphs
+ text_frame.clear()
+ if "\n" not in cell_text:
+ addCell(table.cell(i, j), cell_text, color, fontSize)
+ continue
+
+ for idx, line in enumerate(cell_text.split("\n")):
+ segments = re.split(hyperlink_pattern, line)
+
+ if idx == 0:
+ paragraph = text_frame.paragraphs[0]
+ else:
+ paragraph = text_frame.add_paragraph()
+
+ # paragraph = text_frame.add_paragraph()
+ for i, segment in enumerate(segments):
+ if i % 3 == 0:
+ paragraph.add_run().text = segment
+ elif i % 3 == 1: # url
+ url = segment
+ elif i % 3 == 2: # hyperlink text
+ hyperlink_run = paragraph.add_run()
+ hyperlink_run.hyperlink.address = url
+ hyperlink_run.text = segment
+
+ makeParaBulletPointed(paragraph)
+ paragraph.font.size = Pt(8)
+
+
+def getValuesInColumn(sheet, col1_value):
+ values = []
+ for column_cell in sheet.iter_cols(1, sheet.max_column):
+ if column_cell[0].value == col1_value:
+ j = 0
+ for data in column_cell[1:]:
+ values.append(data.value)
+ break
+ return values
+
+
+def getValuesInColumnForController(sheet, col1_value, controller):
+ values = []
+ col1_index = -1
+ col2_index = -1
+
+ col2_value = "controller"
+
+ # Find the indexes of col1_value and col2_value in the header row
+ for i, cell in enumerate(sheet[1]):
+ if cell.value == col1_value:
+ col1_index = i
+ if cell.value == col2_value:
+ col2_index = i
+ if col1_index != -1 and col2_index != -1:
+ break
+
+ # Check if both columns were found
+ if col1_index == -1 or col2_index == -1:
+ return values
+
+ # Iterate through the rows in the worksheet, starting from row 2 to skip the header row
+ for row in sheet.iter_rows(min_row=2):
+ if row[col2_index].value == controller:
+ values.append(row[col1_index].value)
+
+ return values
+
+
+def getRowCountForController(sheet, controller: str) -> int:
+ CONTROLLER_NAME_COLUMN_INDEX = 1
+ matching_rows = sum(row[0] == controller for row in sheet.iter_rows(min_row=1, max_row=sheet.max_row, min_col=CONTROLLER_NAME_COLUMN_INDEX, max_col=CONTROLLER_NAME_COLUMN_INDEX, values_only=True))
+ return matching_rows
+
+
+def getAppsWithScore(sheet, assessmentScore):
+ values = []
+ for column_cell in sheet.iter_cols(1, sheet.max_column):
+ if column_cell[0].value == "OverallAssessment":
+ j = 0
+ for idx, data in enumerate(column_cell[1:]):
+ if data.value == assessmentScore:
+ values.append(sheet[f"C{idx + 2}"].value) # +2 because of the header
+ break
+ return values
+
+def add_image(slide, image_path, left, top, width, height):
+ return slide.shapes.add_picture(image_path, left, top, width, height)
+
+
+def search(filename, search_path="."):
+ for root, _, files in os.walk(search_path):
+ if filename in files:
+ return os.path.join(root, filename)
+ return None
+
+def markRaceTrackFailures(root, uc: UseCase):
+ # racetrack slide is the second slide
+ slide = root.slides[1]
+ pass_mark = search("checkmark.png", "../")
+ fail_mark = search("xmark.png", "../")
+ image_width = Inches(0.3)
+ image_height = Inches(0.3)
+
+ # IMPORTANT - EXTERNAL DEPENDENCY each pitstop has a named shape in the
+ # PPTX template and is used for the iteration below else pitstop shapes won't be found
+ # and will not be visually marked for fail/pass checkmarks
+ pitstops = ("onboard","use","implement","optimize","adopt","engage")
+
+ shapes_dict = {shape.name: shape for shape in slide.shapes}
+ for pitstop in pitstops:
+ try:
+ if uc.isFailed(pitstop):
+ _ = add_image(slide, fail_mark , shapes_dict[pitstop].left, shapes_dict[pitstop].top, image_width, image_height)
+ else:
+ _ = add_image(slide, pass_mark , shapes_dict[pitstop].left, shapes_dict[pitstop].top, image_width, image_height)
+ except KeyError:
+ logging.error(f"Shape with name '{pitstop}' not found in the slide. This prevents properly marking the race track with visual markers. ")
+
+def createCxHamUseCasePpt(folder: str):
+ logging.info(f"Creating CX HAM Use Case PPT for {folder}")
+ apm_wb = load_workbook(f"output/{folder}/{folder}-MaturityAssessment-apm.xlsx")
+ db_wb = load_workbook(f"output/{folder}/{folder}-AgentMatrix.xlsx")
+
+ # currently only 1st controller in the job file is examined.
+ controller = getValuesInColumn(apm_wb["Analysis"], "controller")[0]
+
+ json_file = search("HybridApplicationMonitoringUseCase.json", "../")
+ uc = UseCase(json_file)
+ _ = calculate_kpis(apm_wb, db_wb, uc)
+
+ template_file = search("HybridApplicationMonitoringUseCase_template.pptx", "../")
+ root = Presentation(template_file)
+
+ ############################# Onboard ###########################
+ generatePitstopHealthCheckTable(controller, root, uc, "onboard")
+ generateRemediationSlides(controller, root, uc, "onboard", "onboard_remediation")
+ ############################# Implement ###########################
+ generatePitstopHealthCheckTable(controller, root, uc, "implement")
+ generateRemediationSlides(controller, root, uc, "implement", "implement_remediation")
+ ############################ Use ##################################
+ generatePitstopHealthCheckTable(controller, root, uc, "use")
+ generateRemediationSlides(controller, root, uc, "use", "use_remediation")
+ ############################ Engage ###############################
+ generatePitstopHealthCheckTable(controller, root, uc, "engage")
+ generateRemediationSlides(controller, root, uc, "engage", "engage_remediation")
+ ############################ Adopt ###############################
+ generatePitstopHealthCheckTable(controller, root, uc, "adopt")
+ generateRemediationSlides(controller, root, uc, "adopt", "adopt_remediation")
+ ########################### Optimize ##############################
+ generatePitstopHealthCheckTable(controller, root, uc, "optimize")
+ generateRemediationSlides(controller, root, uc, "optimize", "optimize_remediation")
+
+ cleanup_slides(root, uc)
+ markRaceTrackFailures(root,uc)
+
+
+ root.save(f"output/{folder}/{folder}-cx-HybridApplicationMonitoringUseCaseMaturityAssessment-presentation.pptx")
+
+
+def generatePitstopHealthCheckTable(folder, root, uc, pitstop):
+ slide = root.slides[uc.getSlideId(pitstop)]
+ data = [["Controller", "Checklist Item", "Tooltips", "Exit Criteria Logic"]]
+ for task in uc.getPitstopTasks(pitstop):
+ data.append([folder, f"{uc.getChecklistItem(task)}", f"{uc.getToolTip(task)}", uc.getHealthCheckStatus(task)])
+ addTable(slide, data, fontSize=10, top=2, left=1.5)
+
+
+def generateRemediationSlides(folder: str, root: Presentation, uc: UseCase, pitstop: str, remediation_slide: str):
+ if uc.pitStopContainsFailureOrManualCheck(pitstop):
+ slide = root.slides[uc.getSlideId(remediation_slide)]
+ data = [["Controller", "Checklist Item", "Recommendation"]]
+ for task in uc.getPitstopTasks(pitstop):
+ data.append(
+ [folder,
+ f"{uc.getChecklistItem(task)}",
+ '\n'.join(value["remediation_item"] for value in uc.getRemediationList(task).values())
+ ]
+ )
+ addRemediationTable(slide, data, fontSize=10, top=2, left=.5)
+
+
+def filter_slides(keep_slide_indexes, prs: Presentation):
+ total_slides = len(prs.slides)
+ slides_to_remove = {i for i in range(total_slides) if i not in keep_slide_indexes}
+ for slide_index in sorted(slides_to_remove, reverse=True):
+ slide = prs.slides[slide_index]
+ id_to_index_mapping = {slide.id: (i, slide.rId) for i, slide in enumerate(prs.slides._sldIdLst)}
+ slide_id = slide.slide_id
+ slide_rId = id_to_index_mapping[slide_id][1]
+ prs.part.drop_rel(slide_rId)
+ del prs.slides._sldIdLst[id_to_index_mapping[slide_id][0]]
+ return prs
+
+
+def cleanup_slides(root: Presentation, uc: UseCase):
+ slides_to_keep = uc.getAllSlideIndexes()
+ if not uc.pitStopContainsFailureOrManualCheck("onboard"):
+ slides_to_keep.remove(uc.getSlideId("onboard_remediation"))
+ if not uc.pitStopContainsFailureOrManualCheck("implement"):
+ slides_to_keep.remove(uc.getSlideId("implement_remediation"))
+ if not uc.pitStopContainsFailureOrManualCheck("use"):
+ slides_to_keep.remove(uc.getSlideId("use_remediation"))
+ if not uc.pitStopContainsFailureOrManualCheck("engage"):
+ slides_to_keep.remove(uc.getSlideId("engage_remediation"))
+ if not uc.pitStopContainsFailureOrManualCheck("adopt"):
+ slides_to_keep.remove(uc.getSlideId("adopt_remediation"))
+ if not uc.pitStopContainsFailureOrManualCheck("optimize"):
+ slides_to_keep.remove(uc.getSlideId("optimize_remediation"))
+
+ filter_slides(slides_to_keep, root)
+
+
+def calculate_kpis(apm_wb, agent_wb, uc: UseCase):
+ # currently only supports one controller report out of the workbook
+ controller = getValuesInColumn(apm_wb["Analysis"], "controller")[0]
+ logging.info(f"processing report for 1st controller only as multiple controllers are not supported yet: {controller}")
+
+ totalApplications = getRowCountForController(apm_wb["Analysis"], controller)
+ percentAgentsReportingData = getValuesInColumnForController(apm_wb["AppAgentsAPM"], "percentAgentsReportingData", controller)
+ countOfpercentAgentsReportingData = len([x for x in percentAgentsReportingData if x >= 1])
+ numberOfBTsList = getValuesInColumnForController(apm_wb["BusinessTransactionsAPM"], "numberOfBTs", controller)
+ customMatchRulesList = getValuesInColumnForController(apm_wb["BusinessTransactionsAPM"], "numberCustomMatchRules", controller)
+ numberOfDataCollectorsConfigured = getValuesInColumnForController(apm_wb["DataCollectorsAPM"], "numberOfDataCollectorFieldsConfigured", controller)
+ countOfNumberOfDataCollectorsConfigured = len([x for x in numberOfDataCollectorsConfigured if x >= 1])
+ numberOfCustomHealthRules = getValuesInColumnForController(apm_wb["HealthRulesAndAlertingAPM"], "numberOfCustomHealthRules", controller)
+ numberOfHealthRuleViolations = getValuesInColumnForController(apm_wb["HealthRulesAndAlertingAPM"], "numberOfHealthRuleViolations", controller)
+ numberOfDefaultHealthRulesModified = getValuesInColumnForController(apm_wb["HealthRulesAndAlertingAPM"], "numberOfDefaultHealthRulesModified", controller)
+ numberOfActionsBoundToEnabledPoliciesList = getValuesInColumnForController(apm_wb["HealthRulesAndAlertingAPM"], "numberOfActionsBoundToEnabledPolicies", controller)
+ dashboardsList = getValuesInColumnForController(apm_wb["DashboardsAPM"], "numberOfDashboards", controller)
+
+ dbAgentList = getValuesInColumnForController(agent_wb["Individual - dbAgents"], "status", controller)
+ dbAgentsActiveCount = len([x for x in dbAgentList if x == "ACTIVE"])
+
+ ### ONB
+ FSO_HAM_ONB_1 = f'manual check'
+ FSO_HAM_ONB_2 = f'manual check'
+ FSO_HAM_ONB_3 = f'manual check'
+ FSO_HAM_ONB_4 = f'manual check'
+
+ ### IMP
+ FSO_HAM_IMP_1 = f'manual check'
+ FSO_HAM_IMP_2 = f'Pass ({totalApplications} applications on-boarded)' if totalApplications >= 1 else f'Fail. At least one application needs to be instrumented and reporting metric data into AppDynamics controller'
+ FSO_HAM_IMP_3 = f'manual check'
+
+ # FSO_HAM_IMP_4 = f'Pass ({customMatchRulesList})' if all(countOfCustomRule >= 2 for countOfCustomRule in customMatchRulesList) else f'Fail (Not all applications have at least 2 custom BT match rules). Only {len([x for x in customMatchRulesList if x >= 2])} have at least 2 custom match rules out of {totalApplications}.'
+ FSO_HAM_IMP_4 = f'Pass' if countOfpercentAgentsReportingData == totalApplications else f'Fail (Not all application agents are reporting data). Only {countOfpercentAgentsReportingData} reporting data out of {totalApplications}.'
+
+ ### USE
+ # FSO_HAM_USE_1 = f'Pass' if countOfpercentAgentsReportingData == totalApplications else f'Fail (Not all application agents are reporting data). Only {countOfpercentAgentsReportingData} reporting data out of {totalApplications}.'
+ FSO_HAM_USE_1 = f'Pass ({customMatchRulesList})' if all(countOfCustomRule >= 2 for countOfCustomRule in customMatchRulesList) else f'Fail (Not all applications have at least 2 custom BT match rules). Only {len([x for x in customMatchRulesList if x >= 2])} have at least 2 custom match rules out of {totalApplications}.'
+
+ FSO_HAM_USE_2 = f'Pass' if all(count >= 5 for count in numberOfBTsList) else f'Fail (Not all applications have at least 5 Business Transactions). Only {len([x for x in numberOfBTsList if x >= 5])} have at least 5 Business Transactions out of {totalApplications}.'
+
+ FSO_HAM_USE_3 = f'Pass' if all(count >= 2 for count in numberOfCustomHealthRules) else f'Fail (At least 2 custom health rules must be configured per application). Only {len([count for count in numberOfCustomHealthRules if count >= 2])} applications have at least 2 custom health rules configured out of {totalApplications}.'
+ FSO_HAM_USE_4 = f'Pass ({countOfNumberOfDataCollectorsConfigured} data collectors found)' if countOfNumberOfDataCollectorsConfigured >= 2 else 'Fail'
+
+ ### ENG
+ FSO_HAM_ENG_1 = f'Pass' if all(count >= 5 for count in numberOfActionsBoundToEnabledPoliciesList) else f'Fail (Not all applications have at least 2 policies with actionable alerts). Only {len([x for x in numberOfActionsBoundToEnabledPoliciesList if x >= 2])} applications have at least 2 actionable alerts out of {totalApplications} applications.'
+
+ FSO_HAM_ENG_2 = f'Pass' if all(count >= 2 for count in dashboardsList) else f'Fail (there should be at least two dashboards configured). Only {len([x for x in dashboardsList if x >= 2])} dashboards are configured'
+ FSO_HAM_ENG_3 = f'manual check'
+ FSO_HAM_ENG_4 = f'manual check'
+
+ ### ADO
+ FSO_HAM_ADO_1 = f'manual check'
+ FSO_HAM_ADO_2 = f'Pass' if dbAgentsActiveCount > 0 else f'Fail (there should be at least one Active Database Agent configured). Currently {dbAgentsActiveCount} Active Database agents are configured.'
+
+ ### OPT
+ FSO_HAM_OPT_1 = f'manual check'
+ FSO_HAM_OPT_2 = f'manual check'
+ FSO_HAM_OPT_3 = f'Pass' if all(count >= 6 for count in numberOfCustomHealthRules) else f'Fail (At least 6 custom health rules must be configured per applications). Only {len([count for count in numberOfCustomHealthRules if count >= 6])} application has 6 custom health rules configured.'
+ FSO_HAM_OPT_4 = f'Pass' if all(count >= 10 for count in numberOfBTsList) else f'Fail (there should be at least 10 Business Translations detected per application). Only {len([count for count in numberOfBTsList if count >= 10])} applications have at least 10 Business Transaction'
+
+ uc.setHealthCheckStatus('FSO_HAM_ONB_1', FSO_HAM_ONB_1)
+ uc.setHealthCheckStatus('FSO_HAM_ONB_2', FSO_HAM_ONB_2)
+ uc.setHealthCheckStatus('FSO_HAM_ONB_3', FSO_HAM_ONB_3)
+ uc.setHealthCheckStatus('FSO_HAM_ONB_4', FSO_HAM_ONB_4)
+ uc.setHealthCheckStatus('FSO_HAM_USE_1', FSO_HAM_USE_1)
+ uc.setHealthCheckStatus('FSO_HAM_USE_2', FSO_HAM_USE_2)
+ uc.setHealthCheckStatus('FSO_HAM_USE_3', FSO_HAM_USE_3)
+ uc.setHealthCheckStatus('FSO_HAM_USE_4', FSO_HAM_USE_4)
+ uc.setHealthCheckStatus('FSO_HAM_IMP_1', FSO_HAM_IMP_1)
+ uc.setHealthCheckStatus('FSO_HAM_IMP_2', FSO_HAM_IMP_2)
+ uc.setHealthCheckStatus('FSO_HAM_IMP_3', FSO_HAM_IMP_3)
+ uc.setHealthCheckStatus('FSO_HAM_IMP_4', FSO_HAM_IMP_4)
+ uc.setHealthCheckStatus('FSO_HAM_ENG_1', FSO_HAM_ENG_1)
+ uc.setHealthCheckStatus('FSO_HAM_ENG_2', FSO_HAM_ENG_2)
+ uc.setHealthCheckStatus('FSO_HAM_ENG_3', FSO_HAM_ENG_3)
+ uc.setHealthCheckStatus('FSO_HAM_ENG_4', FSO_HAM_ENG_4)
+ uc.setHealthCheckStatus('FSO_HAM_ADO_1', FSO_HAM_ADO_1)
+ uc.setHealthCheckStatus('FSO_HAM_ADO_2', FSO_HAM_ADO_2)
+ uc.setHealthCheckStatus('FSO_HAM_OPT_1', FSO_HAM_OPT_1)
+ uc.setHealthCheckStatus('FSO_HAM_OPT_2', FSO_HAM_OPT_2)
+ uc.setHealthCheckStatus('FSO_HAM_OPT_3', FSO_HAM_OPT_3)
+ uc.setHealthCheckStatus('FSO_HAM_OPT_4', FSO_HAM_OPT_4)
+
+ kpi_dictionary = {
+ 'FSO_HAM_ONB_1': FSO_HAM_ONB_1,
+ 'FSO_HAM_ONB_2': FSO_HAM_ONB_2,
+ 'FSO_HAM_ONB_3': FSO_HAM_ONB_3,
+ 'FSO_HAM_ONB_4': FSO_HAM_ONB_4,
+ 'FSO_HAM_USE_1': FSO_HAM_USE_1,
+ 'FSO_HAM_USE_2': FSO_HAM_USE_2,
+ 'FSO_HAM_USE_3': FSO_HAM_USE_3,
+ 'FSO_HAM_USE_4': FSO_HAM_USE_4,
+ 'FSO_HAM_IMP_1': FSO_HAM_IMP_1,
+ 'FSO_HAM_IMP_2': FSO_HAM_IMP_2,
+ 'FSO_HAM_IMP_3': FSO_HAM_IMP_3,
+ 'FSO_HAM_IMP_4': FSO_HAM_IMP_4,
+ 'FSO_HAM_ENG_1': FSO_HAM_ENG_1,
+ 'FSO_HAM_ENG_2': FSO_HAM_ENG_2,
+ 'FSO_HAM_ENG_3': FSO_HAM_ENG_3,
+ 'FSO_HAM_ENG_4': FSO_HAM_ENG_4,
+ 'FSO_HAM_ADO_1': FSO_HAM_ADO_1,
+ 'FSO_HAM_ADO_2': FSO_HAM_ADO_2,
+ 'FSO_HAM_OPT_1': FSO_HAM_OPT_1,
+ 'FSO_HAM_OPT_2': FSO_HAM_OPT_2,
+ 'FSO_HAM_OPT_3': FSO_HAM_OPT_3,
+ 'FSO_HAM_OPT_4': FSO_HAM_OPT_4
+ }
+
+ return kpi_dictionary
diff --git a/backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json b/backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json
new file mode 100644
index 0000000..5637fa2
--- /dev/null
+++ b/backend/resources/pptAssets/HybridApplicationMonitoringUseCase.json
@@ -0,0 +1,406 @@
+{
+ "version": "1.0.0",
+ "UseCase": "Hybrid Application Monitoring",
+ "pitstop": {
+ "onboard": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_ONB_1",
+ "checklist_item": "Understand Full Stack Observability",
+ "tooltip": "Customer has engaged with Cisco Content",
+ "high_level_exit_criteria": "consume the ATX/ACC Listed",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Checkout Hybrid Application Monitoring Guided Resources for a list of actions."
+ },
+ "2": {
+ "remediation_item": "Read Full-Stack Observability from Cisco At-a-Glance."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_ONB_2",
+ "checklist_item": "Understand Full Stack Observability Use Cases",
+ "tooltip": "Customer has attended Project Planning ATX OR has clicked on document / link for project planning or deployment planning",
+ "high_level_exit_criteria": "Complete Project Planning",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Attend Data Center Ask the Experts (ATXs) Sessions by searching for Hybrid Application Monitoring."
+ },
+ "2": {
+ "remediation_item": "Read Full-Stack Observability from Cisco At-a-Glance."
+ }
+ }
+ },
+ "3": {
+ "task_id": "FSO_HAM_ONB_3",
+ "checklist_item": "AppDynamics Getting Started ",
+ "tooltip": "Become familiar with the AppDynamics and Application Performance Monitoring product overview",
+ "high_level_exit_criteria": "Consume the ATX/ACC for best practices of AppDynamics",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "AppDynamics online documentation is available at docs.appdynamics.com."
+ },
+ "2": {
+ "remediation_item": "Check out on-demand and free webcasts covering all aspects of AppDynamics."
+ },
+ "3": {
+ "remediation_item": "View community web site for articles and help"
+ },
+ "4": {
+ "remediation_item": "Check out education portal"
+ }
+ }
+ },
+ "4": {
+ "task_id": "FSO_HAM_ONB_4",
+ "checklist_item": "Create AppDynamics Account",
+ "tooltip": "AppDynamics account created",
+ "high_level_exit_criteria": "Successfully complete AppDynamics Account Creation",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Check out Getting started page and access the Account Management Portal."
+ },
+ "2": {
+ "remediation_item": "Users may also use AppDynamics API to create users and groups."
+ }
+ }
+ }
+ }
+ },
+ "implement": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_IMP_1",
+ "checklist_item": "Confirm AppDynamics Licensing",
+ "tooltip": "Premium or Enterprise license tier with APM",
+ "high_level_exit_criteria": "Confirm Premium or Enterprise license enabled in AppDynamics",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Login to accounts.appdynamics.com and check License Management in Entitlements."
+ },
+ "2": {
+ "remediation_item": "From the accounts page you will be able too lookup all Tenants (controllers) you have access too."
+ },
+ "3": {
+ "remediation_item": "Or checkout the on-demand course Account Management Portal - License Management."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_IMP_2",
+ "checklist_item": "Onboard Application(s) in AppDynamics",
+ "tooltip": "Onboarded application(s) in AppD",
+ "high_level_exit_criteria": "Onboard at least 1 application in AppD",
+ "exit_criteria_logic": "Applications Onboarded >= 1",
+ "min_pass_threshold": "Applications Onboarded >= 1",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Go through the Getting Started for all essentials."
+ },
+ "2": {
+ "remediation_item": "Following the Deployment Planning Guide will help you understanding the essentials before starting the first deployment."
+ },
+ "3": {
+ "remediation_item": "Start with the Install App Server Agents to deploy your first agent."
+ }
+ }
+ },
+ "3": {
+ "task_id": "FSO_HAM_IMP_3",
+ "checklist_item": "Create AppD users, groups and roles",
+ "tooltip": "Number of AppD users, groups and roles created in AppD",
+ "high_level_exit_criteria": "consume the ATX/ACC Listed",
+ "exit_criteria_logic": "TBI",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Find online documentation for Managing Users and Groups."
+ },
+ "2": {
+ "remediation_item": "Users may also use AppDynamics API to create users and groups."
+ }
+ }
+ },
+ "4": {
+ "task_id": "FSO_HAM_IMP_4",
+ "checklist_item": "Application agents configured per application and reporting data",
+ "tooltip": "Number of app server agents deployed per application",
+ "high_level_exit_criteria": "Deploy at least 1 app server agent per application",
+ "exit_criteria_logic": "Number of Application Agents >= 1",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "At least one agent per application must be reporting data. If agent is installed and not reporting any data, please validate your installation."
+ },
+ "2": {
+ "remediation_item": "If an application is no longer being monitored, ensure the agent is uninstalled and the application is removed from the Controller. Uninstalling an agent will also ensure that the license is returned to the pool."
+ }
+ }
+ }
+ }
+ },
+ "use": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_USE_1",
+ "checklist_item": "Create Business Transactions detection rules",
+ "tooltip": "Number of business transactions configured",
+ "high_level_exit_criteria": "At least 1 business transaction rule configured per application using a custom match rules",
+ "exit_criteria_logic": "Number of Business Transactions discovered using custom match rule >= 1",
+ "min_pass_threshold": "Number of Business Transactions discovered using customer match rule >= 1",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Consult online documentation on how to configure Custom Match Rules on a per application basis."
+ },
+ "2": {
+ "remediation_item": "Make sure the application is having load on all the relevant functions. And follow other Best Practices to create Business Transactions."
+ },
+ "3": {
+ "remediation_item": "If no Business Transaction gets discovered check if the technologies used is available in Supported Environments e.g. for the Java Agent: Java Supported Environments."
+ },
+ "4": {
+ "remediation_item": "Live Preview can be a helpful tool to create Custom Match rules if nothing gets discovered."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_USE_2",
+ "checklist_item": "Add Business Transactions to AppDynamics",
+ "tooltip": "Number of business transactions discovered per application (through either Auto Discovery or Custom match rules)",
+ "high_level_exit_criteria": "Add at least 5 Business Transactions per application ",
+ "exit_criteria_logic": "Number of Business Transactions discovered >= 5",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Ensure each monitored application has at least 5 business transactions in Business Transactions showing under Business Transactions tab."
+ },
+ "2": {
+ "remediation_item": "Consult online documentation on how to configure Business Transactions on a per application basis."
+ }
+ }
+ },
+ "3": {
+ "task_id": "FSO_HAM_USE_3",
+ "checklist_item": "Create Health Rules",
+ "tooltip": "Number of health rules configured per application",
+ "high_level_exit_criteria": "Create at least 2 Health rules on per application basis",
+ "exit_criteria_logic": "Number of Health Rules >= 2 per application",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Check out the Health Rules documentation."
+ },
+ "2": {
+ "remediation_item": "Make sure you fine-tune Metric evaluation to avoid false positives and alert spams."
+ }
+ }
+ },
+ "4": {
+ "task_id": "FSO_HAM_USE_4",
+ "checklist_item": "Create Data Collectors and Detection Rules",
+ "tooltip": "Number of data collectors and detection rules created across all applications",
+ "high_level_exit_criteria": "Define Data Collectors, Create Data Collection Rules",
+ "exit_criteria_logic": "Number of Data Collectors >= 1 across all applications(i.e. not per application)",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "See online documentation on how to configure Data Collection Rules on a per application basis."
+ },
+ "2": {
+ "remediation_item": "Checkout this helpful online community post on how to trouble-shoot using data collectors."
+ }
+ }
+ }
+ }
+ },
+ "engage": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_ENG_1",
+ "checklist_item": "Create Policies and corresponding Actions",
+ "tooltip": "At least 2 policies and assigned actions per application",
+ "high_level_exit_criteria": "Create at least 2 Policies and corresponding Actions per application",
+ "exit_criteria_logic": "Number of polices with assigned actions >= 2 per application",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Create Actions as part of a Policy setup."
+ },
+ "2": {
+ "remediation_item": "Consult Online Documentation for configuring Policies and Actions."
+ },
+ "3": {
+ "remediation_item": "Short video on configuring Policies and Actions."
+ },
+ "4": {
+ "remediation_item": "Consult the AppDynamics Education Portal for available resources. For access please reach out to your Cisco/AppDynamics Account Manager."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_ENG_2",
+ "checklist_item": "Create and Use Dashboards",
+ "tooltip": "Number of dashboards created",
+ "high_level_exit_criteria": "Create at least 2 Dashboards across all applications",
+ "exit_criteria_logic": "Number of Dashboards created >=2",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Create Custom Dashboards"
+ },
+ "2": {
+ "remediation_item": "Utilize the new Dash Studio designed to make building dashboards easier."
+ }
+ }
+ },
+ "3": {
+ "task_id": "FSO_HAM_ENG_3",
+ "checklist_item": "Create Reports from Dashboards",
+ "tooltip": "Number of reports scheduled to be sent periodically",
+ "high_level_exit_criteria": "Create at least 2 Reports",
+ "exit_criteria_logic": "TBI",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Consult Online Documentation on how to create dashboards and reports."
+ },
+ "2": {
+ "remediation_item": "Checkout report types that are available"
+ }
+ }
+ },
+ "4": {
+ "task_id": "FSO_HAM_ENG_4",
+ "checklist_item": "Number of AppD users",
+ "tooltip": "Customer has engaged with Cisco Content",
+ "high_level_exit_criteria": "Create at least 5 AppD Users, and has user groups and roles assigned",
+ "exit_criteria_logic": "TBI",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Find online documents for Managing Users and Groups."
+ },
+ "2": {
+ "remediation_item": "See also the Account Management Portal for central management of user accounts."
+ }
+ }
+ }
+ }
+ },
+ "adopt": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_ADO_1",
+ "checklist_item": "Integrate Third party Add-Ons in AppD",
+ "tooltip": "Number of third-party integrations enabled in AppD",
+ "high_level_exit_criteria": "Have at least 1 Third party Integration",
+ "exit_criteria_logic": "TBI",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Consult Online Documentation on how to integrate third party tools with AppDynamics."
+ },
+ "2": {
+ "remediation_item": "See Online Documentation on ways to extend AppDynamics."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_ADO_2",
+ "checklist_item": "Deploy Database and Machine Agents",
+ "tooltip": "Number of database agent deployed",
+ "high_level_exit_criteria": "Deploy at least 1 Database Agent",
+ "exit_criteria_logic": "Number of Database Agents deployed >= 1",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "See Online Documentation on how to deploy Machine agents."
+ },
+ "2": {
+ "remediation_item": "See Online Documentation on how to deploy Database agents."
+ }
+ }
+ }
+ }
+ },
+ "optimize": {
+ "checklist_sequence": {
+ "1": {
+ "task_id": "FSO_HAM_OPT_1",
+ "checklist_item": "Update Business Transaction Thresholds",
+ "tooltip": "Optimize the BT threshold values",
+ "high_level_exit_criteria": "Confirm Business Transaction thresholds are enabled",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "See Online Documentation on how to configure Business Transaction thresholds."
+ },
+ "2": {
+ "remediation_item": "General understanding of Business Transactions is also crucial."
+ }
+ }
+ },
+ "2": {
+ "task_id": "FSO_HAM_OPT_2",
+ "checklist_item": "Automate Remediation actions based on findings",
+ "tooltip": "Actions triggered for remediation, make http request or custom action",
+ "high_level_exit_criteria": "Confirm at least 1 automated action has been triggered",
+ "exit_criteria_logic": "Manual",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "Ensure at least 1 automated action has been triggered"
+ },
+ "2": {
+ "remediation_item": "See Online Documentation on how to configure automated remediation actions."
+ }
+ }
+ },
+ "3": {
+ "task_id": "FSO_HAM_OPT_3",
+ "checklist_item": "Create at least 5 Customer Health Rules per application",
+ "tooltip": "Number of Health Rules",
+ "high_level_exit_criteria": "Create at least 6 Health Checks",
+ "exit_criteria_logic": "Number of Custom Health Rules(per application) Configured >= 5",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "See Online Documentation on how to create Health Rules."
+ },
+ "2": {
+ "remediation_item": "See AppDynamics APIs for managing Health Rules programmatically."
+ }
+ }
+ },
+ "4": {
+ "task_id": "FSO_HAM_OPT_4",
+ "checklist_item": "Observe at least 10 Business Transactions per application (observed through auto-discovery or custom rule)",
+ "tooltip": "Number of business transactions configured",
+ "high_level_exit_criteria": "Add at least 10 Business Transactions per application",
+ "exit_criteria_logic": "Business Transaction count >= 10 (per application)",
+ "min_pass_threshold": "",
+ "remediation_steps": {
+ "1": {
+ "remediation_item": "See Online Documentation on how to configure Business Transactions."
+ },
+ "2": {
+ "remediation_item": "Monitoring Business Transactions is crucial in identifying performance issues."
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx b/backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx
new file mode 100644
index 0000000..134432c
Binary files /dev/null and b/backend/resources/pptAssets/HybridApplicationMonitoringUseCase_template.pptx differ
diff --git a/backend/resources/pptAssets/checkmark.png b/backend/resources/pptAssets/checkmark.png
new file mode 100644
index 0000000..2e215e7
Binary files /dev/null and b/backend/resources/pptAssets/checkmark.png differ
diff --git a/backend/resources/pptAssets/xmark.png b/backend/resources/pptAssets/xmark.png
new file mode 100644
index 0000000..cb36519
Binary files /dev/null and b/backend/resources/pptAssets/xmark.png differ
diff --git a/input/thresholds/DefaultThresholds.json b/input/thresholds/DefaultThresholds.json
index 8b34557..c7aa0c4 100644
--- a/input/thresholds/DefaultThresholds.json
+++ b/input/thresholds/DefaultThresholds.json
@@ -1,5 +1,5 @@
{
- "version": "v1.5.9.4",
+ "version": "v1.5.9.5",
"apm": {
"AppAgentsAPM": {
"platinum": {
diff --git a/tests/test_api.py b/tests/test_api.py
index 70a901c..cb86835 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -403,7 +403,7 @@ async def testGetDataCollectorUsage(controller):
True,
) in dataCollectorUsage.data["allDataCollectors"]
- assert not ("HTTP Parameter", "foo", True) in dataCollectorUsage.data["dataCollectorsPresentInSnapshots"]
+ assert not ("HTTP Parameter", "bar", True) in dataCollectorUsage.data["dataCollectorsPresentInSnapshots"]
assert (
"Business Data",
"in_snapshot_not_analytics",
@@ -616,7 +616,7 @@ async def testGetMachineAgents(controller):
async def testGetCustomMetrics(controller):
assert (await controller.loginToController()).error is None
- customMetrics = await controller.getCustomMetrics(APPLICATION_ID, "machine-agent")
+ customMetrics = await controller.getCustomMetrics(APPLICATION_ID, "customer-services")
assert customMetrics.error is None
assert len(customMetrics.data) > 0
diff --git a/tests/test_ham_report.py b/tests/test_ham_report.py
new file mode 100644
index 0000000..8d26ee3
--- /dev/null
+++ b/tests/test_ham_report.py
@@ -0,0 +1,119 @@
+import os
+
+import pytest
+from pptx import Presentation
+from output.presentations.cxPptFsoUseCases import UseCase, generateRemediationSlides, cleanup_slides, generatePitstopHealthCheckTable, createCxHamUseCasePpt, markRaceTrackFailures
+from output.presentations.cxPptFsoUseCases import UseCase
+
+
+
+
+
+def search(filename, search_path="."):
+ for root, _, files in os.walk(search_path):
+ if filename in files:
+ return os.path.join(root, filename)
+ return None
+
+
+@pytest.fixture
+def uc():
+ json_file = search("HybridApplicationMonitoringUseCase.json", "../")
+ return UseCase(json_file)
+
+def testCxPptFsoUseCases(uc):
+ template = search("HybridApplicationMonitoringUseCase_template.pptx", "../")
+ root = Presentation(template)
+ data = uc.pitstop_data
+ uc.setHealthCheckStatus("FSO_HAM_ONB_1", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ONB_2", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ONB_3", "fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ONB_4", "fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_IMP_1", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_IMP_2", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_IMP_3", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_IMP_4", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ADO_1", "Fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ADO_2", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_OPT_1", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_OPT_2", "Fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_OPT_3", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_OPT_4", "Fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_USE_1", "Fail (xxx hc failed)")
+ uc.setHealthCheckStatus("FSO_HAM_USE_2", "pass (xxx hc )")
+
+ uc.setHealthCheckStatus("FSO_HAM_ENG_1", "fail (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ENG_2", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ENG_3", "pass (xxx hc )")
+ uc.setHealthCheckStatus("FSO_HAM_ENG_4", "fail (xxx hc )")
+
+ markRaceTrackFailures(root,uc)
+
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "onboard")
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "implement")
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "use")
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "engage")
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "adopt")
+ generatePitstopHealthCheckTable("folder_xxx", root, uc, "optimize")
+
+ generateRemediationSlides("folder_xxx", root, uc, "onboard", "onboard_remediation")
+ generateRemediationSlides("folder_xxx", root, uc, "implement", "implement_remediation")
+ generateRemediationSlides("folder_xxx", root, uc, "engage", "engage_remediation")
+ generateRemediationSlides("folder_xxx", root, uc, "adopt", "adopt_remediation")
+ generateRemediationSlides("folder_xxx", root, uc, "optimize", "optimize_remediation")
+ generateRemediationSlides("folder_xxx", root, uc, "use", "use_remediation")
+ cleanup_slides(root, uc)
+ root.save(f"cx-ham-usecase-test-presentation.pptx")
+
+def test_getPitstopTasks(uc):
+ # onboard has 4 sequence or tasks
+ assert len(uc.getPitstopTasks("onboard")) == 4
+ # implement has 4 sequence or tasks
+ assert len(uc.getPitstopTasks("implement")) == 4
+ # use has 4 sequence or tasks
+ assert len(uc.getPitstopTasks("use")) == 4
+ # engage has 4 sequence or tasks
+ assert len(uc.getPitstopTasks("engage")) == 4
+ # adopt has 2 sequence or tasks
+ assert len(uc.getPitstopTasks("adopt")) == 2
+ # optimize has 4 sequence or tasks
+ assert len(uc.getPitstopTasks("optimize")) == 4
+
+
+ assert uc.getPitstopTasks("onboard")[0] == "FSO_HAM_ONB_1"
+ assert uc.getPitstopTasks("onboard")[1] == "FSO_HAM_ONB_2"
+ assert uc.getPitstopTasks("onboard")[2] == "FSO_HAM_ONB_3"
+ assert uc.getPitstopTasks("onboard")[3] == "FSO_HAM_ONB_4"
+
+
+ assert uc.getPitstopTasks("implement")[0] == "FSO_HAM_IMP_1"
+ assert uc.getPitstopTasks("implement")[1] == "FSO_HAM_IMP_2"
+ assert uc.getPitstopTasks("implement")[2] == "FSO_HAM_IMP_3"
+ assert uc.getPitstopTasks("implement")[3] == "FSO_HAM_IMP_4"
+
+
+ assert uc.getPitstopTasks("use")[0] == "FSO_HAM_USE_1"
+ assert uc.getPitstopTasks("use")[1] == "FSO_HAM_USE_2"
+ assert uc.getPitstopTasks("use")[2] == "FSO_HAM_USE_3"
+ assert uc.getPitstopTasks("use")[3] == "FSO_HAM_USE_4"
+
+ assert uc.getPitstopTasks("engage")[0] == "FSO_HAM_ENG_1"
+ assert uc.getPitstopTasks("engage")[1] == "FSO_HAM_ENG_2"
+ assert uc.getPitstopTasks("engage")[2] == "FSO_HAM_ENG_3"
+ assert uc.getPitstopTasks("engage")[3] == "FSO_HAM_ENG_4"
+
+ assert uc.getPitstopTasks("adopt")[0] == "FSO_HAM_ADO_1"
+ assert uc.getPitstopTasks("adopt")[1] == "FSO_HAM_ADO_2"
+ try:
+ assert uc.getPitstopTasks("adopt")[2] == "FSO_HAM_ADO_3"
+ except IndexError as e:
+ assert e.args[0] == "list index out of range"
+
+ assert uc.getPitstopTasks("optimize")[0] == "FSO_HAM_OPT_1"
+ assert uc.getPitstopTasks("optimize")[1] == "FSO_HAM_OPT_2"
+ assert uc.getPitstopTasks("optimize")[2] == "FSO_HAM_OPT_3"
+ assert uc.getPitstopTasks("optimize")[3] == "FSO_HAM_OPT_4"
+
+@pytest.mark.skip(reason="need to refactor to work")
+def test_ReportUsingDemoControllerOuput():
+ createCxHamUseCasePpt("appd-cse")
\ No newline at end of file