diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index 8e36120d7..8febe03bb 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -365,8 +365,9 @@ def shutdown(self, response: Response): # Check that Testrun is not currently running if (self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED, + TestrunStatus.COMPLETE, TestrunStatus.IDLE ]): LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress") @@ -527,12 +528,14 @@ async def delete_device(self, request: Request, response: Response): if (self._session.get_target_device() == device and self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT + TestrunStatus.COMPLETE, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED ]): + response.status_code = 403 return self._generate_msg( - False, "Cannot delete this device whilst " + "it is being tested") + False, "Cannot delete this device whilst it is being tested") # Delete device self._testrun.delete_device(device) @@ -546,7 +549,7 @@ async def delete_device(self, request: Request, response: Response): LOGGER.error(e) response.status_code = 500 return self._generate_msg( - False, "An error occured whilst deleting " + "the device") + False, "An error occured whilst deleting the device") async def save_device(self, request: Request, response: Response): LOGGER.debug("Received device post request") @@ -650,8 +653,9 @@ async def edit_device(self, request: Request, response: Response): if (self._session.get_target_device() == device and self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT + TestrunStatus.COMPLETE, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED ]): response.status_code = 403 return self._generate_msg( diff --git a/framework/python/src/common/statuses.py b/framework/python/src/common/statuses.py index 967e98981..33516390e 100644 --- a/framework/python/src/common/statuses.py +++ b/framework/python/src/common/statuses.py @@ -15,21 +15,26 @@ class TestrunStatus: - """Enum for all possible Testrun statuses""" + """Statuses for overall testing""" IDLE = "Idle" STARTING = "Starting" WAITING_FOR_DEVICE = "Waiting for Device" MONITORING = "Monitoring" IN_PROGRESS = "In Progress" CANCELLED = "Cancelled" - COMPLIANT = "Compliant" - NON_COMPLIANT = "Non-Compliant" STOPPING = "Stopping" VALIDATING = "Validating Network" + COMPLETE = "Complete" + PROCEED = "Proceed" + DO_NOT_PROCEED = "Do Not Proceed" +class TestrunResult: + """Statuses for the Testrun result""" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" class TestResult: - """Enum for all possible test results""" + """Statuses for test results""" IN_PROGRESS = "In Progress" COMPLIANT = "Compliant" NON_COMPLIANT = "Non-Compliant" diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index cae5045c9..fbd993a8e 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -17,7 +17,8 @@ from weasyprint import HTML from io import BytesIO from common import util, logger -from common.statuses import TestrunStatus +from common.statuses import TestrunStatus, TestrunResult +from test_orc import test_pack import base64 import os from test_orc.test_case import TestCase @@ -31,7 +32,10 @@ TESTS_FIRST_PAGE = 11 TESTS_PER_PAGE = 20 TEST_REPORT_STYLES = 'test_report_styles.css' -TEST_REPORT_TEMPLATE = 'test_report_template.html' +TEMPLATES_FOLDER = 'report_templates' +TEST_REPORT_TEMPLATE = 'report_template.html' +ICON = 'icon.png' + LOGGER = logger.get_logger('REPORT') @@ -54,13 +58,14 @@ class TestReport(): """Represents a previous Testrun report.""" def __init__(self, - status=TestrunStatus.NON_COMPLIANT, + result=TestrunResult.NON_COMPLIANT, started=None, finished=None, total_tests=0): self._device = {} self._mac_addr = None - self._status: str = status + self._status: TestrunStatus = TestrunStatus.COMPLETE + self._result: TestrunResult = result self._started = started self._finished = finished self._total_tests = total_tests @@ -82,6 +87,9 @@ def add_module_templates(self, module_templates): def get_status(self): return self._status + def get_result(self): + return self._result + def get_started(self): return self._started @@ -117,6 +125,7 @@ def to_json(self): report_json['mac_addr'] = self._mac_addr report_json['device'] = self._device report_json['status'] = self._status + report_json['result'] = self._result report_json['started'] = self._started.strftime(DATE_TIME_FORMAT) report_json['finished'] = self._finished.strftime(DATE_TIME_FORMAT) @@ -170,6 +179,10 @@ def from_json(self, json_file): self._device['device_profile'] = json_file['device']['additional_info'] self._status = json_file['status'] + + if 'result' in json_file: + self._result = json_file['result'] + self._started = datetime.strptime(json_file['started'], DATE_TIME_FORMAT) self._finished = datetime.strptime(json_file['finished'], DATE_TIME_FORMAT) @@ -209,13 +222,22 @@ def to_pdf(self): def to_html(self): + # Obtain test pack + current_test_pack = test_pack.TestPack.get_test_pack( + self._device['test_pack']) + template_folder = os.path.join(current_test_pack.path, + TEMPLATES_FOLDER) # Jinja template template_env = Environment( - loader=FileSystemLoader(report_resource_dir), + loader=FileSystemLoader( + template_folder + ), trim_blocks=True, lstrip_blocks=True ) template = template_env.get_template(TEST_REPORT_TEMPLATE) + + # Report styles with open(os.path.join(report_resource_dir, TEST_REPORT_STYLES), 'r', @@ -227,13 +249,11 @@ def to_html(self): with open(test_run_img_file, 'rb') as f: logo = base64.b64encode(f.read()).decode('utf-8') - json_data=self.to_json() + # Icon + with open(os.path.join(template_folder, ICON), 'rb') as f: + icon = base64.b64encode(f.read()).decode('utf-8') - # Icons - with open(qualification_icon, 'rb') as f: - icon_qualification = base64.b64encode(f.read()).decode('utf-8') - with open(pilot_icon, 'rb') as f: - icon_pilot = base64.b64encode(f.read()).decode('utf-8') + json_data=self.to_json() # Convert the timestamp strings to datetime objects start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') @@ -249,29 +269,27 @@ def to_html(self): successful_tests += 1 # Obtain the steps to resolve - steps_to_resolve = self._get_steps_to_resolve(json_data) + logic = current_test_pack.get_logic() + steps_to_resolve_ = logic.get_steps_to_resolve(json_data) - # Obtain optional recommendations - optional_steps_to_resolve = self._get_optional_steps_to_resolve(json_data) + LOGGER.debug(steps_to_resolve_) module_reports = self._module_reports env_module = Environment(loader=BaseLoader()) pages_num = self._pages_num(json_data) module_templates = [ env_module.from_string(s).render( - json_data=json_data, + name=current_test_pack.name, device=json_data['device'], logo=logo, - icon_qualification=icon_qualification, - icon_pilot=icon_pilot, + icon=icon, version=self._version, ) for s in self._module_templates ] return self._add_page_counter(template.render(styles=styles, logo=logo, - icon_qualification=icon_qualification, - icon_pilot=icon_pilot, + icon=icon, version=self._version, json_data=json_data, device=json_data['device'], @@ -281,8 +299,7 @@ def to_html(self): successful_tests=successful_tests, total_tests=self._total_tests, test_results=json_data['tests']['results'], - steps_to_resolve=steps_to_resolve, - optional_steps_to_resolve=optional_steps_to_resolve, + steps_to_resolve=steps_to_resolve_, module_reports=module_reports, pages_num=pages_num, tests_first_page=TESTS_FIRST_PAGE, @@ -297,7 +314,7 @@ def _add_page_counter(self, html): total_pages = len(page_index_divs) for index, div in enumerate(page_index_divs): div.string = f'Page {index+1}/{total_pages}' - return soup.prettify() + return str(soup) def _pages_num(self, json_data): @@ -337,23 +354,3 @@ def _device_modules(self, device): reverse=True) ) return sorted_modules - - def _get_steps_to_resolve(self, json_data): - tests_with_recommendations = [] - - # Collect all tests with recommendations - for test in json_data['tests']['results']: - if 'recommendations' in test: - tests_with_recommendations.append(test) - - return tests_with_recommendations - - def _get_optional_steps_to_resolve(self, json_data): - tests_with_recommendations = [] - - # Collect all tests with recommendations - for test in json_data['tests']['results']: - if 'optional_recommendations' in test: - tests_with_recommendations.append(test) - - return tests_with_recommendations diff --git a/framework/python/src/core/session.py b/framework/python/src/core/session.py index 480316e51..4df431502 100644 --- a/framework/python/src/core/session.py +++ b/framework/python/src/core/session.py @@ -20,7 +20,7 @@ from fastapi.encoders import jsonable_encoder from common import util, logger, mqtt from common.risk_profile import RiskProfile -from common.statuses import TestrunStatus, TestResult +from common.statuses import TestrunStatus, TestResult, TestrunResult from net_orc.ip_control import IPControl # Certificate dependencies @@ -85,6 +85,7 @@ def __init__(self, root_dir): self._root_dir = root_dir self._status = TestrunStatus.IDLE + self._result = None self._description = None # Target test device @@ -133,6 +134,7 @@ def __init__(self, root_dir): # System network interfaces self._ifaces = {} + # Loading methods self._load_version() self._load_config() @@ -386,12 +388,18 @@ def get_ipv4_subnet(self): def get_ipv6_subnet(self): return self._ipv6_subnet - def get_status(self): + def get_status(self) -> TestrunStatus: return self._status - def set_status(self, status): + def set_status(self, status: TestrunStatus): self._status = status + def get_result(self) -> TestrunResult: + return self._result + + def set_result(self, result: TestrunResult): + self._result = result + def set_description(self, desc: str): self._description = desc @@ -807,6 +815,7 @@ def delete_profile(self, profile): def reset(self): self.set_status(TestrunStatus.IDLE) + self.set_result(None) self.set_description(None) self.set_target_device(None) self._report_url = None @@ -838,6 +847,9 @@ def to_json(self): 'tests': results } + if self.get_result() is not None: + session_json['result'] = self.get_result() + if self._report_url is not None: session_json['report'] = self.get_report_url() diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 428df8474..f3fe33c80 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -488,10 +488,7 @@ def _device_stable(self, mac_addr): LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') self._set_status(TestrunStatus.IN_PROGRESS) - result = self._test_orc.run_test_modules() - - if result is not None: - self._set_status(result) + self._test_orc.run_test_modules() self._stop_network() diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index deca20798..ae65c0f87 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -22,7 +22,7 @@ from datetime import datetime from common import logger, util from common.testreport import TestReport -from common.statuses import TestrunStatus, TestResult +from common.statuses import TestrunStatus, TestrunResult, TestResult from core.docker.test_docker_module import TestModule from test_orc.test_case import TestCase from test_orc.test_pack import TestPack @@ -37,6 +37,8 @@ RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, "test") TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") +TEST_PACK_CONFIG_FILE = "config.json" +TEST_PACK_LOGIC_FILE = "test_pack.py" TEST_MODULES_DIR = "modules/test" MODULE_CONFIG = "conf/module_config.json" @@ -175,6 +177,7 @@ def run_test_modules(self): report = TestReport() generated_report_json = self._generate_report() + report.from_json(generated_report_json) report.add_module_reports(self.get_session().get_module_reports()) report.add_module_templates(self.get_session().get_module_templates()) @@ -190,13 +193,17 @@ def run_test_modules(self): # Default message is empty (better than an error message). # This should never be shown message: str = "" - if report.get_status() == TestrunStatus.COMPLIANT: + if report.get_result() == TestrunResult.COMPLIANT: message = test_pack.get_message("compliant_description") - elif report.get_status() == TestrunStatus.NON_COMPLIANT: + elif report.get_result() == TestrunResult.NON_COMPLIANT: message = test_pack.get_message("non_compliant_description") self.get_session().set_description(message) + # Set result and status at the end + self.get_session().set_result(report.get_result()) + self.get_session().set_status(report.get_status()) + # Move testing output from runtime to local device folder self._timestamp_results(device) @@ -205,8 +212,6 @@ def run_test_modules(self): LOGGER.debug("Old test results cleaned") - return report.get_status() - def _write_reports(self, test_report): out_dir = os.path.join( @@ -231,41 +236,40 @@ def _write_reports(self, test_report): def _generate_report(self): + device = self.get_session().get_target_device() + test_pack_name = device.test_pack + test_pack = self.get_test_pack(test_pack_name) + report = {} report["testrun"] = {"version": self.get_session().get_version()} - report["mac_addr"] = self.get_session().get_target_device().mac_addr - report["device"] = self.get_session().get_target_device().to_dict() + report["mac_addr"] = device.mac_addr + report["device"] = device.to_dict() report["started"] = self.get_session().get_started().strftime( "%Y-%m-%d %H:%M:%S") report["finished"] = self.get_session().get_finished().strftime( "%Y-%m-%d %H:%M:%S") - report["status"] = self._calculate_result() + + # Update the result + result = test_pack.get_logic().calculate_result( + self.get_session().get_test_results()) + report["result"] = result + + # Update the status + status = test_pack.get_logic().calculate_status( + result, + self.get_session().get_test_results()) + report["status"] = status + report["tests"] = self.get_session().get_report_tests() report["report"] = ( self._api_url + "/" + SAVED_DEVICE_REPORTS.replace( "{device_folder}", - self.get_session().get_target_device().device_folder) + + device.device_folder) + self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S")) return report - def _calculate_result(self): - result = TestResult.COMPLIANT - for test_result in self.get_session().get_test_results(): - - # Check Required tests - if (test_result.required_result.lower() == "required" and - test_result.result not in [TestResult.COMPLIANT, TestResult.ERROR]): - result = TestResult.NON_COMPLIANT - - # Check Required if Applicable tests - elif (test_result.required_result.lower() == "required if applicable" - and test_result.result == TestResult.NON_COMPLIANT): - result = TestResult.NON_COMPLIANT - - return result - def _cleanup_old_test_results(self, device): if device.max_device_reports is not None: @@ -659,19 +663,7 @@ def _get_module_container(self, module): def _load_test_packs(self): - for test_pack_file in os.listdir(TEST_PACKS_DIR): - - LOGGER.debug(f"Loading test pack {test_pack_file}") - - with open(os.path.join(self._root_path, TEST_PACKS_DIR, test_pack_file), - encoding="utf-8") as f: - test_pack_json = json.load(f) - - test_pack: TestPack = TestPack(name=test_pack_json["name"], - tests=test_pack_json["tests"], - language=test_pack_json["language"]) - - self._test_packs.append(test_pack) + self._test_packs = TestPack.get_test_packs() def _load_test_modules(self): """Load network modules from module_config.json.""" @@ -734,10 +726,7 @@ def get_test_packs(self) -> List[TestPack]: return self._test_packs def get_test_pack(self, name: str) -> TestPack: - for test_pack in self._test_packs: - if test_pack.name.lower() == name.lower(): - return test_pack - return None + return TestPack.get_test_pack(name, self._test_packs) def _stop_modules(self, kill=False): LOGGER.info("Stopping test modules") diff --git a/framework/python/src/test_orc/test_pack.py b/framework/python/src/test_orc/test_pack.py index a2e7c5f97..eb9c852c2 100644 --- a/framework/python/src/test_orc/test_pack.py +++ b/framework/python/src/test_orc/test_pack.py @@ -13,9 +13,20 @@ # limitations under the License. """Represents a testing pack.""" +from types import ModuleType from typing import List, Dict from dataclasses import dataclass, field from collections import defaultdict +import os +import sys +import json +import importlib + +RESOURCES_DIR = "resources" + +TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") +TEST_PACK_CONFIG_FILE = "config.json" +TEST_PACK_LOGIC_FILE = "test_pack.py" @dataclass @@ -26,6 +37,8 @@ class TestPack: # pylint: disable=too-few-public-methods,too-many-instance-attr description: str = "" tests: List[dict] = field(default_factory=lambda: []) language: Dict = field(default_factory=lambda: defaultdict(dict)) + pack_logic: ModuleType = None + path: str = "" def get_test(self, test_name: str) -> str: """Get details of a test from the test pack""" @@ -44,6 +57,9 @@ def get_required_result(self, test_name: str) -> str: return "Informational" + def get_logic(self): + return self.pack_logic + def get_message(self, name: str) -> str: if name in self.language: return self.language[name] @@ -56,3 +72,62 @@ def to_dict(self): "tests": self.tests, "language": self.language } + + @staticmethod + def load_logic(source, module_name=None): + """Reads file source and loads it as a module""" + + spec = importlib.util.spec_from_file_location(module_name, source) + module = importlib.util.module_from_spec(spec) + + # Add the module to sys.modules + sys.modules[module_name] = module + + # Execute the module + spec.loader.exec_module(module) + + return module + + @staticmethod + def get_test_packs() -> List["TestPack"]: + + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) + test_packs = [] + + for test_pack_folder in os.listdir(TEST_PACKS_DIR): + test_pack_path = os.path.join( + root_path, + TEST_PACKS_DIR, + test_pack_folder + ) + + with open(os.path.join( + test_pack_path, + TEST_PACK_CONFIG_FILE), encoding="utf-8") as f: + test_pack_json = json.load(f) + + test_pack: TestPack = TestPack( + name = test_pack_json["name"], + tests = test_pack_json["tests"], + language = test_pack_json["language"], + pack_logic = TestPack.load_logic( + os.path.join(test_pack_path, TEST_PACK_LOGIC_FILE), + "test_pack_" + test_pack_folder + "_logic" + ), + path = test_pack_path + ) + test_packs.append(test_pack) + + return test_packs + + @staticmethod + def get_test_pack(name: str, test_packs: List["TestPack"]=None) -> "TestPack": + if test_packs is None: + test_packs = TestPack.get_test_packs() + for test_pack in test_packs: + if test_pack.name.lower() == name.lower(): + return test_pack + return None diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index 47afff71f..c1db567ae 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -51,10 +51,10 @@ def __init__(self, # pylint: disable=R0917 def generate_module_report(self): # Load Jinja2 template - page_max_height = 910 + page_max_height = 850 header_height = 48 summary_height = 135 - row_height = 42 + row_height = 44 loader=FileSystemLoader(self._report_template_folder) template = Environment( loader=loader, diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 300a250fd..40b2d26dc 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -53,6 +53,7 @@ import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; import { TestingType } from './model/device'; +import { ResultOfTestrun, StatusOfTestrun } from './model/testrun-status'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -499,7 +500,8 @@ describe('AppStore', () => { store.overrideSelector(selectIsTestingComplete, true); store.overrideSelector(selectSystemStatus, { - status: 'Compliant', + result: ResultOfTestrun.Compliant, + status: StatusOfTestrun.Complete, mac_addr: '00:1e:42:35:73:c4', device: { manufacturer: 'Delta', diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index a12a536a3..9b07fddf7 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -53,7 +53,7 @@ import { setTestModules, updateAdapters, } from './store/actions'; -import { StatusOfTestrun, TestrunStatus } from './model/testrun-status'; +import { ResultOfTestrun, TestrunStatus } from './model/testrun-status'; import { Adapters, SettingMissedError, @@ -333,7 +333,7 @@ export class AppStore extends ComponentStore { filter(([isTestingComplete]) => isTestingComplete === true), filter( ([, testrunStatus]) => - testrunStatus?.status === StatusOfTestrun.Compliant && + testrunStatus?.result === ResultOfTestrun.Compliant && testrunStatus?.device.test_pack === TestingType.Pilot ), tap(() => { diff --git a/modules/ui/src/app/components/download-report/download-report.component.ts b/modules/ui/src/app/components/download-report/download-report.component.ts index dcabc2281..8184ef6fc 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.ts +++ b/modules/ui/src/app/components/download-report/download-report.component.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { StatusOfTestrun, TestrunStatus } from '../../model/testrun-status'; +import { ResultOfTestrun, TestrunStatus } from '../../model/testrun-status'; import { CommonModule, DatePipe } from '@angular/common'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ReportActionComponent } from '../report-action/report-action.component'; @@ -40,16 +40,16 @@ export class DownloadReportComponent extends ReportActionComponent { } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware - } ${data.status} ${this.getFormattedDateString(data.started)}` + } ${data.result ? data.result : data.status} ${this.getFormattedDateString(data.started)}` .replace(/ /g, '_') .toLowerCase(); } getClass(data: TestrunStatus) { - if (data.status === StatusOfTestrun.Compliant) { + if (data.result === ResultOfTestrun.Compliant) { return `${this.class}-compliant`; } - if (data.status === StatusOfTestrun.NonCompliant) { + if (data.result === ResultOfTestrun.NonCompliant) { return `${this.class}-non-compliant`; } return this.class; diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html index acecbd760..61ba85614 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html @@ -28,12 +28,15 @@ class="testing-result" id="testing-result-main-info" [class]=" - data.testrunStatus.status === StatusOfTestrun.Compliant || + (data.testrunStatus.result === ResultOfTestrun.Compliant && + data.testrunStatus.status === StatusOfTestrun.Complete) || data.testrunStatus.status === StatusOfTestrun.Proceed ? 'success-result' : 'failed-result' "> -

{{ data.testrunStatus.status }}

+

+ {{ getTestingResult(data.testrunStatus) }} +

{{ data.testrunStatus.description }}

diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index 199d89bf5..bb4c54b62 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -25,7 +25,11 @@ import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; import { Router, RouterLink } from '@angular/router'; -import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; +import { + TestrunStatus, + StatusOfTestrun, + ResultOfTestrun, +} from '../../model/testrun-status'; import { DownloadReportComponent } from '../download-report/download-report.component'; import { Subject, takeUntil, timer } from 'rxjs'; import { FocusManagerService } from '../../services/focus-manager.service'; @@ -80,6 +84,7 @@ export class DownloadZipModalComponent } as Profile; public readonly Routes = Routes; public readonly StatusOfTestrun = StatusOfTestrun; + public readonly ResultOfTestrun = ResultOfTestrun; profiles: Profile[] = []; selectedProfile: Profile; constructor( @@ -165,6 +170,13 @@ export class DownloadZipModalComponent return this.testRunService.getRiskClass(riskResult); } + public getTestingResult(data: TestrunStatus): string { + if (data.status === StatusOfTestrun.Complete && data.result) { + return data.result; + } + return data.status; + } + private getZipLink(reportURL: string): string { return reportURL.replace('report', 'export'); } diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index 2889b0571..0c127b18c 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -5,7 +5,8 @@ import { DeviceStatus, TestingType } from '../model/device'; export const HISTORY = [ { mac_addr: '01:02:03:04:05:06', - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -20,7 +21,8 @@ export const HISTORY = [ finished: '2023-06-23T10:17:10.123Z', }, { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, @@ -37,7 +39,8 @@ export const HISTORY = [ }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -56,7 +59,8 @@ export const HISTORY = [ export const HISTORY_AFTER_REMOVE = [ { mac_addr: '01:02:03:04:05:06', - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -72,7 +76,8 @@ export const HISTORY_AFTER_REMOVE = [ }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -86,11 +91,12 @@ export const HISTORY_AFTER_REMOVE = [ started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, -]; +] as TestrunStatus[]; export const FORMATTED_HISTORY = [ { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:06', device: { status: DeviceStatus.VALID, @@ -106,11 +112,13 @@ export const FORMATTED_HISTORY = [ finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, @@ -126,12 +134,14 @@ export const FORMATTED_HISTORY = [ finished: '2023-07-23T10:17:10.123Z', deviceFirmware: '1.2.3', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -146,10 +156,11 @@ export const FORMATTED_HISTORY = [ finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, -]; +] as HistoryTestrun[]; export const FILTERS = { deviceInfo: 'test', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index aa2ec4a8e..48321f2d5 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -16,6 +16,7 @@ import { IResult, RequiredResult, + ResultOfTestrun, StatusOfTestrun, TestrunStatus, TestsData, @@ -95,12 +96,13 @@ export const TEST_DATA: TestsData = { }; const PROGRESS_DATA_RESPONSE = ( - status: string, + status: StatusOfTestrun, finished: string | null, tests: TestsData | IResult[], - report: string = '' + report: string = '', + result?: ResultOfTestrun ) => { - return { + const response = { status, mac_addr: '01:02:03:04:05:06', device: { @@ -115,7 +117,11 @@ const PROGRESS_DATA_RESPONSE = ( tests, report, tags: ['VSA', 'Other tag', 'And one more'], - }; + } as TestrunStatus; + if (result) { + response.result = result; + } + return response; }; export const MOCK_PROGRESS_DATA_CANCELLING: TestrunStatus = @@ -126,18 +132,20 @@ export const MOCK_PROGRESS_DATA_IN_PROGRESS_EMPTY: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, []); export const MOCK_PROGRESS_DATA_COMPLIANT: TestrunStatus = PROGRESS_DATA_RESPONSE( - StatusOfTestrun.Compliant, + StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf' + 'https://api.testrun.io/report.pdf', + ResultOfTestrun.Compliant ); export const MOCK_PROGRESS_DATA_NON_COMPLIANT: TestrunStatus = PROGRESS_DATA_RESPONSE( - StatusOfTestrun.NonCompliant, + StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf' + 'https://api.testrun.io/report.pdf', + ResultOfTestrun.NonCompliant ); export const MOCK_PROGRESS_DATA_CANCELLED: TestrunStatus = diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index c7c92e695..04596d5e4 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -17,7 +17,8 @@ import { Device } from './device'; export interface TestrunStatus { mac_addr: string | null; - status: string; + status: StatusOfTestrun; + result?: ResultOfTestrun; description?: string; device: IDevice; started: string | null; @@ -30,6 +31,7 @@ export interface TestrunStatus { export interface HistoryTestrun extends TestrunStatus { deviceFirmware: string; deviceInfo: string; + testResult: string; program: string; duration: string; } @@ -59,24 +61,28 @@ export enum RequiredResult { RequiredIfApplicable = 'Required if Applicable', } +export enum ResultOfTestrun { + Compliant = 'Compliant', // used for Completed + NonCompliant = 'Non-Compliant', // used for Completed +} + export enum StatusOfTestrun { InProgress = 'In Progress', WaitingForDevice = 'Waiting for Device', Cancelled = 'Cancelled', Cancelling = 'Cancelling', Failed = 'Failed', - Compliant = 'Compliant', // used for Completed CompliantLimited = 'Compliant (Limited)', CompliantHigh = 'Compliant (High)', - NonCompliant = 'Non-Compliant', // used for Completed - SmartReady = 'Smart Ready', // used for Completed + SmartReady = 'Smart Ready', Idle = 'Idle', Monitoring = 'Monitoring', Starting = 'Starting', Error = 'Error', Validating = 'Validating Network', - Proceed = 'Proceed', - DoNotProceed = 'Do Not Proceed', + Complete = 'Complete', // device qualification + Proceed = 'Proceed', // pilot assessment + DoNotProceed = 'Do Not Proceed', // pilot assessment } export enum StatusOfTestResult { diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts index 3d8ac231a..6dd78f401 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts @@ -58,7 +58,10 @@ import { DateRange as LocalDateRange, } from '../../../../model/filters'; import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { StatusOfTestResult } from '../../../../model/testrun-status'; +import { + ResultOfTestrun, + StatusOfTestrun, +} from '../../../../model/testrun-status'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; interface DialogData { @@ -97,8 +100,10 @@ export class FilterDialogComponent implements OnInit { resultList = [ - { value: StatusOfTestResult.Compliant, enabled: false }, - { value: StatusOfTestResult.NonCompliant, enabled: false }, + { value: ResultOfTestrun.Compliant, enabled: false }, + { value: ResultOfTestrun.NonCompliant, enabled: false }, + { value: StatusOfTestrun.Proceed, enabled: false }, + { value: StatusOfTestrun.DoNotProceed, enabled: false }, ]; filterForm!: FormGroup; selectedRangeValue!: DateRange | undefined; diff --git a/modules/ui/src/app/pages/reports/reports.component.html b/modules/ui/src/app/pages/reports/reports.component.html index beb538149..c187a6c27 100644 --- a/modules/ui/src/app/pages/reports/reports.component.html +++ b/modules/ui/src/app/pages/reports/reports.component.html @@ -147,9 +147,9 @@

Reports

- {{ data.status }} + {{ data.testResult }} diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index 7b13bf5e9..de0bf20c3 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -12,6 +12,7 @@ import { selectReports, selectRiskProfiles } from '../../store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; import { fetchReports, setReports } from '../../store/actions'; +import { TestingType } from '../../model/device'; export interface ReportsComponentState { displayedColumns: string[]; @@ -252,12 +253,24 @@ export class ReportsStore extends ComponentStore { ...item, deviceFirmware: item.device.firmware, deviceInfo: item.device.manufacturer + ' ' + item.device.model, + testResult: this.getTestResult(item), duration: this.getDuration(item.started, item.finished), program: item.device.test_pack ?? '', }; }); } + private getTestResult(item: TestrunStatus): string { + let result = ''; + if (item.device.test_pack === TestingType.Qualification) { + result = item.result ? item.result : item.status; + } + if (item.device.test_pack === TestingType.Pilot) { + result = item.status; + } + return result; + } + private getDuration(started: string | null, finished: string | null): string { if (!started || !finished) { return ''; @@ -290,7 +303,7 @@ export class ReportsStore extends ComponentStore { const isIncludeStatus = searchString.results?.length === 0 || - searchString.results?.includes(data.status); + searchString.results?.includes(data.testResult); const isIncludeStartedDate = this.filterStartedDateRange( data.started, searchString diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts index 81b4a1fee..814584eb0 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts @@ -26,7 +26,7 @@ import { MatSelectModule } from '@angular/material/select'; import { CommonModule, DatePipe } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { - StatusOfTestrun, + ResultOfTestrun, TestrunStatus, } from '../../../../model/testrun-status'; import { MatOptionSelectionChange } from '@angular/material/core'; @@ -113,9 +113,9 @@ export class DownloadOptionsComponent { sendGAEvent(data: TestrunStatus, type: string) { let event = `download_report_${type === DownloadOption.PDF ? 'pdf' : 'zip'}`; - if (data.status === StatusOfTestrun.Compliant) { + if (data.result === ResultOfTestrun.Compliant) { event += '_compliant'; - } else if (data.status === StatusOfTestrun.NonCompliant) { + } else if (data.result === ResultOfTestrun.NonCompliant) { event += '_non_compliant'; } // @ts-expect-error data layer is not null diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index c6fb361fb..ca8009264 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -16,7 +16,8 @@