From fc0c44ddddff7b04bdc0cceb52df3b951c774104 Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Thu, 24 Oct 2024 03:18:23 +0100
Subject: [PATCH 1/7] [COMP-428] changing color scheme

---
 .../src/scss/layout/_components.scss            | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/compendium-frontend/src/scss/layout/_components.scss b/compendium-frontend/src/scss/layout/_components.scss
index 1090f2c0..56d4d388 100644
--- a/compendium-frontend/src/scss/layout/_components.scss
+++ b/compendium-frontend/src/scss/layout/_components.scss
@@ -197,15 +197,14 @@
 }
 
 $year-colors: (
-  #fd7f6f,
-  #7eb0d5,
-  #ffee65,
-  #bd7ebe,
-  #beb9db,
-  #b2e061,
-  #ffb55a,
-  #fdcce5,
-  #8bd3c7
+  #CE3D5B,
+  #1B90AC,
+  #FF8D5A,
+  #FF1790,
+  #1E82B6,
+  #13AC9C,
+  #5454A8,
+  #8C6896
 );
 
 $service-tab-colors: (
-- 
GitLab


From 267e8378de492ddc79689ebab58bc4ed193e45a4 Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Fri, 25 Oct 2024 00:03:15 +0100
Subject: [PATCH 2/7] [COMP-367]: SNP Services types for different users

---
 compendium-frontend/src/App.tsx               |  2 +
 compendium-frontend/src/Schema.tsx            |  5 ++
 .../src/components/PolicySidebar.tsx          |  5 ++
 .../helpers/ConnectivityDisplayConstant.tsx   | 15 +++++
 .../src/helpers/dataconversion.tsx            |  2 +-
 .../src/pages/CompendiumData.tsx              |  5 ++
 .../src/pages/SNPServicesOffered.tsx          | 62 +++++++++++++++++++
 .../routes/standards_and_policies.py          | 55 +++++++++++++++-
 8 files changed, 149 insertions(+), 2 deletions(-)
 create mode 100644 compendium-frontend/src/pages/SNPServicesOffered.tsx

diff --git a/compendium-frontend/src/App.tsx b/compendium-frontend/src/App.tsx
index 80fdf7d6..d082f350 100644
--- a/compendium-frontend/src/App.tsx
+++ b/compendium-frontend/src/App.tsx
@@ -48,6 +48,7 @@ import SurveyManagementComponent from './survey/SurveyManagementComponent';
 import UserManagementComponent from './survey/UserManagementComponent';
 import SurveyContainerComponent from "./survey/SurveyContainerComponent";
 import SurveyLanding from "./survey/Landing";
+import SNPServicesOfferedPage from "./pages/SNPServicesOffered";
 
 
 const router = createBrowserRouter([
@@ -103,6 +104,7 @@ const router = createBrowserRouter([
   { path: "/crisis-management", element: <SNPStandardsCrisisManagementPage /> },
   { path: "/crisis-exercise", element: <SNPStandardsCrisisExercisesPage /> },
   { path: "/security-control", element: <SNPStandardsSecurityControlsPage /> },
+  { path: "/services-offered", element: <SNPServicesOfferedPage /> },
   { path: "survey/admin/surveys", element: <SurveyManagementComponent /> },
   { path: "survey/admin/users", element: <UserManagementComponent /> },
   { path: "survey/admin/inspect/:year", element: <SurveyContainerComponent loadFrom={'/api/response/inspect/'} /> },
diff --git a/compendium-frontend/src/Schema.tsx b/compendium-frontend/src/Schema.tsx
index f4f0ba42..af4843ef 100644
--- a/compendium-frontend/src/Schema.tsx
+++ b/compendium-frontend/src/Schema.tsx
@@ -272,6 +272,11 @@ export interface ConnectivityGrowth extends NrenAndYearDatapoint {
     growth: number
 }
 
+export interface ServiceOffered extends NrenAndYearDatapoint {
+    user_category: string,
+    service_category: string
+}
+
 export interface User {
     id: string,
     email: string,
diff --git a/compendium-frontend/src/components/PolicySidebar.tsx b/compendium-frontend/src/components/PolicySidebar.tsx
index 8ac9d21d..7669de15 100644
--- a/compendium-frontend/src/components/PolicySidebar.tsx
+++ b/compendium-frontend/src/components/PolicySidebar.tsx
@@ -38,6 +38,11 @@ const PolicySidebar = () => {
                     <span>Security Controls Used by NRENs</span>
                 </Link>
             </Row>
+            <Row>
+                <Link to="/services-offered" className="link-text-underline">
+                    <span>Services Offered by NRENs by Types of Users</span>
+                </Link>
+            </Row>
         </Sidebar>
     )
 }
diff --git a/compendium-frontend/src/helpers/ConnectivityDisplayConstant.tsx b/compendium-frontend/src/helpers/ConnectivityDisplayConstant.tsx
index b8ad3762..57a6a8d4 100644
--- a/compendium-frontend/src/helpers/ConnectivityDisplayConstant.tsx
+++ b/compendium-frontend/src/helpers/ConnectivityDisplayConstant.tsx
@@ -36,6 +36,11 @@ export const questionMap = {
     "growth": "What do you expect the traffic growth to be in the next 3 years?",
     "charging": "What are the typical charging levels for the following types of commercial connections?",
     "commercial": "What types of commercial organisations do you connect?",
+    "services": <>
+        <span>
+            The table below shows the different types of users served by NRENs. Selecting the institution type will expand the detail to show the categories of services offered by NRENs, with a tick indicating that the NREN offers a specific category of service to the type of user.
+        </span>
+    </>
 }
 
 export const dataKeyMap = {
@@ -83,6 +88,16 @@ export const dataKeyMap = {
         "Yes - Including transit to other networks": "yes_incl_other",
         "Yes - only if sponsored by a connected institution": "yes_if_sponsored",
 
+    },
+    "services": {
+        "Identity/T&I": "identity",
+        "Multimedia": "multimedia",
+        "Professional services": "professional_services",
+        "Network services": "network_services",
+        "Collaboration": "collaboration",
+        "Security": "security",
+        "Storage and Hosting": "storage_and_hosting",
+        "ISP support": "isp_support"
     }
 };
 
diff --git a/compendium-frontend/src/helpers/dataconversion.tsx b/compendium-frontend/src/helpers/dataconversion.tsx
index 9e73b337..ad6c17cc 100644
--- a/compendium-frontend/src/helpers/dataconversion.tsx
+++ b/compendium-frontend/src/helpers/dataconversion.tsx
@@ -462,7 +462,7 @@ export function createConnectivityDataLookupForCarrier<Datatype extends NrenAndY
         if (!nrenEntry) {
             nrenEntry = new Map<number, Map<string, Datatype>>();
         }
-        let yearEntry = nrenEntry.get(entry['carry_mechanism']);
+        let yearEntry = nrenEntry.get(entry[enumCategory]);
         if (!yearEntry) {
             yearEntry = new Map<string, Datatype>();
         }
diff --git a/compendium-frontend/src/pages/CompendiumData.tsx b/compendium-frontend/src/pages/CompendiumData.tsx
index ee97c8f3..943a4da4 100644
--- a/compendium-frontend/src/pages/CompendiumData.tsx
+++ b/compendium-frontend/src/pages/CompendiumData.tsx
@@ -125,6 +125,11 @@ function CompendiumData(): ReactElement {
                                         <span>Security Controls Used by NRENs</span>
                                     </Link>
                                 </Row>
+                                <Row>
+                                    <Link to="/services-offered" className="link-text-underline">
+                                        <span>Services Offered by NRENs by Types of Users</span>
+                                    </Link>
+                                </Row>
 
                             </div>
                         </CollapsibleBox>
diff --git a/compendium-frontend/src/pages/SNPServicesOffered.tsx b/compendium-frontend/src/pages/SNPServicesOffered.tsx
new file mode 100644
index 00000000..ce52bc6a
--- /dev/null
+++ b/compendium-frontend/src/pages/SNPServicesOffered.tsx
@@ -0,0 +1,62 @@
+import React, { useContext } from "react";
+
+import { ServiceOffered } from "../Schema";
+import {
+    createConnectivityDataLookupForCarrier
+} from "../helpers/dataconversion";
+import DataPage from "../components/DataPage";
+import Filter from "../components/graphing/Filter";
+import { Sections } from "../helpers/constants";
+import { FilterSelectionContext } from "../providers/FilterSelectionProvider";
+import ChartContainer from "../components/graphing/ChartContainer";
+import { useData } from "../helpers/useData";
+import { ConnectedUserData } from "../helpers/ConnectivityViewComponent";
+import { questionMap, dataKeyMap, displayTitle } from "../helpers/ConnectivityDisplayConstant";
+
+function SNPServicesOfferedPage(): React.ReactElement {
+    const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
+    const apiUrl = '/api/standards-and-policies/services-offered';
+    const { data: connectedProportion, years, nrens } = useData<ServiceOffered>(apiUrl, setFilterSelection);
+    let isTickIcon = true;
+    const selectedData = (connectedProportion).filter(data =>
+        filterSelection.selectedYears.includes(data.year)
+        && filterSelection.selectedNrens.includes(data.nren)
+    );
+
+    const connectedProportionDict = {};
+    selectedData.forEach(connectedProportion => {
+        connectedProportionDict[connectedProportion.user_category] = connectedProportion.user_category;
+    });
+    let dataLookup: any;
+    let carrierCategoryList: any = ['service_category'];
+    dataLookup = createConnectivityDataLookupForCarrier(selectedData, 'user_category', 'service_category');
+
+
+    const filterNode = <Filter
+        filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
+        filterSelection={filterSelection}
+        setFilterSelection={setFilterSelection}
+    />
+
+    return (
+        <DataPage title="Services Offered by NRENs by Types of Users"
+            description={questionMap["services"]}
+            category={Sections.Policy} filter={filterNode}
+            data={selectedData} filename="nren_services_offered">
+            <>
+                <div style={{ fontSize: "16px", marginBottom: "10px", fontWeight: "bold" }}>
+                </div>
+                <ChartContainer>
+                    <ConnectedUserData
+                        dataLookup={dataLookup}
+                        filterSelection={filterSelection}
+                        tableDataMap={dataKeyMap["services"]}
+                        isTickIcon={isTickIcon}
+                        carrierCategoryList={carrierCategoryList}
+                    />
+                </ChartContainer>
+            </>
+        </DataPage>
+    );
+}
+export default SNPServicesOfferedPage;
diff --git a/compendium_v2/routes/standards_and_policies.py b/compendium_v2/routes/standards_and_policies.py
index 3f33bbff..981ccecb 100644
--- a/compendium_v2/routes/standards_and_policies.py
+++ b/compendium_v2/routes/standards_and_policies.py
@@ -1,6 +1,6 @@
 from typing import Any
 
-from compendium_v2.db.presentation_models import Standards, CrisisExercises, SecurityControls
+from compendium_v2.db.presentation_models import Standards, CrisisExercises, SecurityControls, ServiceUserTypes
 from compendium_v2.routes import common
 from flask import Blueprint, jsonify
 
@@ -67,6 +67,29 @@ SECURITY_CONTROLS_RESPONSE_SCHEMA = {
     'items': {'$ref': '#/definitions/security_controls'}
 }
 
+SERVICES_USER_TYPES = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'definitions': {
+        'policy': {
+            'type': 'object',
+            'properties': {
+                'nren': {'type': 'string'},
+                'nren_country': {'type': 'string'},
+                'year': {'type': 'integer'},
+                'user_category': {'type': 'string'},
+                'service_category': {'type': 'string'}
+
+            },
+            'required': ['nren', 'nren_country', 'year', 'user_category', 'service_category'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/connected/proportion'}
+}
+
+
 
 def standards_extract_data(standards: Standards) -> dict:
     return {
@@ -202,3 +225,33 @@ def security_controls_view() -> Any:
         entries.append(security_controls_extract_data(entry))
 
     return jsonify(entries)
+
+
+def service_user_type_extract_data(service_user_types: ServiceUserTypes):
+    return {
+        'nren': service_user_types.nren.name,
+        'nren_country': service_user_types.nren.country,
+        'year': service_user_types.year,
+        'user_category': service_user_types.user_category.value,
+        'service_category': service_user_types.service_category.value
+        if service_user_types.service_category is not None and service_user_types.service_category.value else '',
+    }
+
+
+@routes.route('/services-offered', methods=['GET'])
+@common.require_accepts_json
+def service_user_types_view() -> Any:
+    """
+    handler for /api/standards-and-policies/services-offered/ requests
+    returns policy information for each NREN/year combination
+
+    response will be formatted as:
+
+    .. asjson::
+        compendium_v2.routes.standards_and_policies.SERVICES_USER_TYPES
+
+    :return:
+    """
+
+    entries = [service_user_type_extract_data(entry) for entry in common.get_data(ServiceUserTypes)]
+    return jsonify(entries)
-- 
GitLab


From 1816c26c8680f631fb1a4fc6bf7fed2ff5e9ee2c Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Fri, 25 Oct 2024 10:43:22 +0100
Subject: [PATCH 3/7] [COMP-367]: fix SNP Services types for different users

---
 .../src/helpers/ConnectivityViewComponent.tsx | 13 ++++++----
 .../src/helpers/dataconversion.tsx            | 24 +++++++++++++++++++
 .../src/pages/SNPServicesOffered.tsx          |  4 ++--
 3 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/compendium-frontend/src/helpers/ConnectivityViewComponent.tsx b/compendium-frontend/src/helpers/ConnectivityViewComponent.tsx
index 2fe9b21b..0c1649b3 100644
--- a/compendium-frontend/src/helpers/ConnectivityViewComponent.tsx
+++ b/compendium-frontend/src/helpers/ConnectivityViewComponent.tsx
@@ -43,13 +43,18 @@ function TableSectionForTickIcon({ userCategoryMapForCarrier, title, dataKey, ca
     const data: any[] = [];
     userCategoryMapForCarrier.forEach((nrensMap: Map<string, Map<string, any>>) => {
         nrensMap.forEach((yearMap: Map<string, any>) => {
+            let matchFound = false;
             yearMap.forEach((entry: any) => {
-                if (dataKey == entry[category])
+
+                if (dataKey == entry[category]) {
                     data.push(entry[category]);
-                else
-                    data.push(null);
-            })
+                    matchFound = true;
+                }
 
+            })
+            if (!matchFound) {
+                data.push(null);
+            }
         });
     });
     return <TableRowForTickIcon title={title} data={data} />;
diff --git a/compendium-frontend/src/helpers/dataconversion.tsx b/compendium-frontend/src/helpers/dataconversion.tsx
index ad6c17cc..8519fb78 100644
--- a/compendium-frontend/src/helpers/dataconversion.tsx
+++ b/compendium-frontend/src/helpers/dataconversion.tsx
@@ -474,6 +474,30 @@ export function createConnectivityDataLookupForCarrier<Datatype extends NrenAndY
     return dataLookup;
 }
 
+export function createConnectivityDataLookupForServicesOffered<Datatype extends NrenAndYearDatapoint>(data: Datatype[], columnProperty: string, enumCategory) {
+    const dataLookup = new Map<string, Map<string, Map<number, Map<string, Datatype>>>>();
+
+    data.forEach(entry => {
+        let serviceEntry = dataLookup.get(entry[columnProperty]);
+        if (!serviceEntry) {
+            serviceEntry = new Map<string, Map<number, Map<string, Datatype>>>();
+        }
+        let nrenEntry = serviceEntry.get(entry.nren);
+        if (!nrenEntry) {
+            nrenEntry = new Map<number, Map<string, Datatype>>();
+        }
+        let yearEntry = nrenEntry.get(entry.year);
+        if (!yearEntry) {
+            yearEntry = new Map<string, Datatype>();
+        }
+        yearEntry.set(entry[enumCategory], entry)
+        nrenEntry.set(entry.year, yearEntry);
+        serviceEntry.set(entry.nren, nrenEntry);
+        dataLookup.set(entry[columnProperty], serviceEntry);
+    });
+    return dataLookup;
+}
+
 export function createConnectivityDataLookupForCommercial<Datatype extends NrenAndYearDatapoint>(data: Datatype[], commercialCategory: string[]) {
     const dataLookup = new Map<string, Map<string, Map<number, Map<string, Datatype>>>>();
 
diff --git a/compendium-frontend/src/pages/SNPServicesOffered.tsx b/compendium-frontend/src/pages/SNPServicesOffered.tsx
index ce52bc6a..c2421a98 100644
--- a/compendium-frontend/src/pages/SNPServicesOffered.tsx
+++ b/compendium-frontend/src/pages/SNPServicesOffered.tsx
@@ -2,7 +2,7 @@ import React, { useContext } from "react";
 
 import { ServiceOffered } from "../Schema";
 import {
-    createConnectivityDataLookupForCarrier
+    createConnectivityDataLookupForServicesOffered
 } from "../helpers/dataconversion";
 import DataPage from "../components/DataPage";
 import Filter from "../components/graphing/Filter";
@@ -29,7 +29,7 @@ function SNPServicesOfferedPage(): React.ReactElement {
     });
     let dataLookup: any;
     let carrierCategoryList: any = ['service_category'];
-    dataLookup = createConnectivityDataLookupForCarrier(selectedData, 'user_category', 'service_category');
+    dataLookup = createConnectivityDataLookupForServicesOffered(selectedData, 'user_category', 'service_category');
 
 
     const filterNode = <Filter
-- 
GitLab


From 91c5c47dcbe46a5bcc5ca4d46e930c3af14ae230 Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Fri, 25 Oct 2024 15:29:03 +0100
Subject: [PATCH 4/7] [COMP-363]: SNP Corporate Strategy

---
 compendium-frontend/src/App.tsx               |  2 +
 compendium-frontend/src/Schema.tsx            |  4 +
 .../src/components/PolicySidebar.tsx          |  5 ++
 .../src/pages/CompendiumData.tsx              |  6 +-
 .../src/pages/SNPCorporateStrategy.tsx        | 89 +++++++++++++++++++
 compendium_v2/routes/policy.py                | 48 ++++++++++
 6 files changed, 153 insertions(+), 1 deletion(-)
 create mode 100644 compendium-frontend/src/pages/SNPCorporateStrategy.tsx

diff --git a/compendium-frontend/src/App.tsx b/compendium-frontend/src/App.tsx
index d082f350..cc6e5388 100644
--- a/compendium-frontend/src/App.tsx
+++ b/compendium-frontend/src/App.tsx
@@ -49,6 +49,7 @@ import UserManagementComponent from './survey/UserManagementComponent';
 import SurveyContainerComponent from "./survey/SurveyContainerComponent";
 import SurveyLanding from "./survey/Landing";
 import SNPServicesOfferedPage from "./pages/SNPServicesOffered";
+import SNPCorporateStrategyPage from "./pages/SNPCorporateStrategy";
 
 
 const router = createBrowserRouter([
@@ -105,6 +106,7 @@ const router = createBrowserRouter([
   { path: "/crisis-exercise", element: <SNPStandardsCrisisExercisesPage /> },
   { path: "/security-control", element: <SNPStandardsSecurityControlsPage /> },
   { path: "/services-offered", element: <SNPServicesOfferedPage /> },
+  { path: "/corporate-strategy", element: <SNPCorporateStrategyPage /> },
   { path: "survey/admin/surveys", element: <SurveyManagementComponent /> },
   { path: "survey/admin/users", element: <UserManagementComponent /> },
   { path: "survey/admin/inspect/:year", element: <SurveyContainerComponent loadFrom={'/api/response/inspect/'} /> },
diff --git a/compendium-frontend/src/Schema.tsx b/compendium-frontend/src/Schema.tsx
index af4843ef..4377d24e 100644
--- a/compendium-frontend/src/Schema.tsx
+++ b/compendium-frontend/src/Schema.tsx
@@ -161,6 +161,10 @@ export interface Policy extends NrenAndYearDatapoint {
     strategic_plan: string
 }
 
+export interface CorporateStrategy extends NrenAndYearDatapoint {
+    strategic_plan: string
+}
+
 export interface ConnectedInstitutionURLs extends NrenAndYearDatapoint {
     urls: string[]
 }
diff --git a/compendium-frontend/src/components/PolicySidebar.tsx b/compendium-frontend/src/components/PolicySidebar.tsx
index 7669de15..34f55fa4 100644
--- a/compendium-frontend/src/components/PolicySidebar.tsx
+++ b/compendium-frontend/src/components/PolicySidebar.tsx
@@ -43,6 +43,11 @@ const PolicySidebar = () => {
                     <span>Services Offered by NRENs by Types of Users</span>
                 </Link>
             </Row>
+            <Row>
+                <Link to="/corporate-strategy" className="link-text-underline">
+                    <span>NREN Corporate Strategies </span>
+                </Link>
+            </Row>
         </Sidebar>
     )
 }
diff --git a/compendium-frontend/src/pages/CompendiumData.tsx b/compendium-frontend/src/pages/CompendiumData.tsx
index 943a4da4..4fa8255d 100644
--- a/compendium-frontend/src/pages/CompendiumData.tsx
+++ b/compendium-frontend/src/pages/CompendiumData.tsx
@@ -130,7 +130,11 @@ function CompendiumData(): ReactElement {
                                         <span>Services Offered by NRENs by Types of Users</span>
                                     </Link>
                                 </Row>
-
+                                <Row>
+                                    <Link to="/corporate-strategy" className="link-text-underline">
+                                        <span>NREN Corporate Strategies </span>
+                                    </Link>
+                                </Row>
                             </div>
                         </CollapsibleBox>
 
diff --git a/compendium-frontend/src/pages/SNPCorporateStrategy.tsx b/compendium-frontend/src/pages/SNPCorporateStrategy.tsx
new file mode 100644
index 00000000..ba856c57
--- /dev/null
+++ b/compendium-frontend/src/pages/SNPCorporateStrategy.tsx
@@ -0,0 +1,89 @@
+import React, { useContext } from 'react';
+import { Table } from "react-bootstrap";
+
+import { CorporateStrategy, Policy } from "../Schema";
+import { createDataLookup, getLatestData } from '../helpers/dataconversion';
+import DataPage from '../components/DataPage';
+import Filter from "../components/graphing/Filter"
+import { Sections } from '../helpers/constants';
+import { FilterSelectionContext } from '../providers/FilterSelectionProvider';
+import { useData } from '../helpers/useData';
+import ChartContainer from '../components/graphing/ChartContainer';
+
+function getJSXFromMap(data: Map<string, Map<number, CorporateStrategy>>) {
+    const policies = [
+        ['acceptable_use', 'Acceptable Use Policy'],
+        ['connectivity', 'Connectivity Policy'],
+        ['data_protection', 'Data Protection Policy'],
+        ['environmental', 'Environmental Policy'],
+        ['equal_opportunity', 'Equal Opportunity Policy'],
+        ['gender_equality', 'Gender Equality Plan'],
+        ['privacy_notice', 'Privacy Notice'],
+        ['strategic_plan', 'Strategic Plan']
+    ];
+
+    return Array.from(data.entries()).map(([nren, nrenMap]) => {
+        return Array.from(nrenMap.entries()).map(([year, policy], yearIndex) => (
+            <tr key={nren + year} className='dotted-border'>
+                <td className='pt-3 nren-column text-nowrap'>{yearIndex == 0 && nren}</td>
+                <td className='pt-3 year-column'>{year}</td>
+                <td className='pt-3 blue-column'>
+                    <ul className='no-list-style-type'>
+                        {policies.map(([key, text]) => (
+                            !!policy[key] && (
+                                <li key={key}>
+                                    <a href={policy[key]} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
+                                        {text}
+                                    </a>
+                                </li>
+                            )
+                        ))}
+                    </ul>
+                </td>
+            </tr>
+        ))
+    })
+}
+
+function SNPCorporateStrategyPage() {
+    const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
+    const { data: fetchedData, years, nrens } = useData<CorporateStrategy>('/api/policy/corporate-strategy', setFilterSelection);
+
+    const policyData = fetchedData ? getLatestData(fetchedData) : [];
+
+    const selectedData = policyData.filter(project =>
+        filterSelection.selectedNrens.includes(project.nren)
+    );
+    const policyDataByYear = createDataLookup(selectedData);
+
+    const filterNode = <Filter
+        filterOptions={{ availableYears: [], availableNrens: [...nrens.values()] }}
+        filterSelection={filterSelection}
+        setFilterSelection={setFilterSelection}
+    />
+
+    return (
+        <DataPage title="NREN Corporate Strategies"
+            description='The table below contains links to the NRENs most recent corporate strategic plans. 
+            NRENs are asked if updates have been made to their corporate strategy over the previous year. 
+            To avoid showing outdated links, just the most recent responses are shown.'
+            category={Sections.Policy} filter={filterNode}
+            data={selectedData} filename='nren_corporate_strategy'>
+            <ChartContainer>
+                <Table borderless className='compendium-table'>
+                    <thead>
+                        <tr>
+                            <th className='nren-column'>NREN</th>
+                            <th className='year-column'>Year</th>
+                            <th className='blue-column'>Corporate Strategy</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {getJSXFromMap(policyDataByYear)}
+                    </tbody>
+                </Table>
+            </ChartContainer>
+        </DataPage>
+    )
+}
+export default SNPCorporateStrategyPage;
diff --git a/compendium_v2/routes/policy.py b/compendium_v2/routes/policy.py
index 1bd10ae1..2ebd74a2 100644
--- a/compendium_v2/routes/policy.py
+++ b/compendium_v2/routes/policy.py
@@ -36,6 +36,26 @@ POLICY_RESPONSE_SCHEMA = {
     'items': {'$ref': '#/definitions/policy'}
 }
 
+CORPORATE_STRATEGY_RESPONSE_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'definitions': {
+        'strategy': {
+            'type': 'object',
+            'properties': {
+                'nren': {'type': 'string'},
+                'nren_country': {'type': 'string'},
+                'year': {'type': 'integer'},
+                'strategic_plan': {'type': 'string'}
+            },
+            'required': ['nren', 'nren_country', 'year', 'strategic_plan'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/strategy'}
+}
+
 
 def policy_extract_data(entry: Policy):
     return {
@@ -70,3 +90,31 @@ def policy_view() -> Any:
 
     entries = [policy_extract_data(entry) for entry in common.get_data(Policy)]
     return jsonify(entries)
+
+
+def corporate_strategy_extract_data(entry: Policy):
+    return {
+        'nren': entry.nren.name,
+        'nren_country': entry.nren.country,
+        'year': entry.year,
+        'strategic_plan': entry.strategic_plan,
+    }
+
+
+@routes.route('/corporate-strategy', methods=['GET'])
+@common.require_accepts_json
+def corporate_strategy_view() -> Any:
+    """
+    handler for /api/policy/corporate-strategy requests
+    returns policy information for each NREN/year combination
+
+    response will be formatted as:
+
+    .. asjson::
+        compendium_v2.routes.policy.CORPORATE_STRATEGY_RESPONSE_SCHEMA
+
+    :return:
+    """
+
+    entries = [corporate_strategy_extract_data(entry) for entry in common.get_data(Policy)]
+    return jsonify(entries)
-- 
GitLab


From b6355644658f5db86c212f2fdce22cc194b7cfe6 Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Fri, 25 Oct 2024 23:53:21 +0100
Subject: [PATCH 5/7] [COMP-358][COMP-408]

---
 .../src/components/NetworkSidebar.tsx         |  2 +-
 .../src/pages/CompendiumData.tsx              |  2 +-
 .../src/pages/NetworkAlienWave.tsx            | 36 +++++++++++--------
 .../src/pages/NetworkTrafficVolume.tsx        |  4 +--
 4 files changed, 26 insertions(+), 18 deletions(-)

diff --git a/compendium-frontend/src/components/NetworkSidebar.tsx b/compendium-frontend/src/components/NetworkSidebar.tsx
index a6e6467b..43781c02 100644
--- a/compendium-frontend/src/components/NetworkSidebar.tsx
+++ b/compendium-frontend/src/components/NetworkSidebar.tsx
@@ -7,7 +7,7 @@ const NetworkSidebar = () => {
     return (
         <Sidebar>
             <h5>Network</h5>
-            <h6 className="section-title" >Connectivity</h6>
+            <h6 className="section-title" >Capacity</h6>
             <Row>
                 <Link to="/traffic-volume" className="link-text-underline">
                     <span>NREN Traffic - NREN Customers & External Networks</span>
diff --git a/compendium-frontend/src/pages/CompendiumData.tsx b/compendium-frontend/src/pages/CompendiumData.tsx
index 4fa8255d..27d13fc5 100644
--- a/compendium-frontend/src/pages/CompendiumData.tsx
+++ b/compendium-frontend/src/pages/CompendiumData.tsx
@@ -188,7 +188,7 @@ function CompendiumData(): ReactElement {
                         </CollapsibleBox>
                         <CollapsibleBox title={Sections.Network} startCollapsed>
                             <div className="collapsible-column">
-                                <h6 className="section-title" >Connectivity</h6>
+                                <h6 className="section-title" >Capacity</h6>
                                 <Row>
                                     <Link to="/traffic-volume" className="link-text-underline">
                                         <span>NREN Traffic - NREN Customers & External Networks</span>
diff --git a/compendium-frontend/src/pages/NetworkAlienWave.tsx b/compendium-frontend/src/pages/NetworkAlienWave.tsx
index c7134d50..f7647a65 100644
--- a/compendium-frontend/src/pages/NetworkAlienWave.tsx
+++ b/compendium-frontend/src/pages/NetworkAlienWave.tsx
@@ -34,11 +34,13 @@ function NetworkAlienWavePage(): React.ReactElement {
         <DataPage title="NREN Use of 3rd Party Alienwave/Lightpath Services"
             description="The table below shows NREN usage of alien wavelength or lightpath services provided by third parties. 
             It does not include alien waves used internally inside the NRENs own networks, as that is covered in another table. 
-
             In the optical network world, the term “alien wavelength” or “alien wave” (AW) is used to describe wavelengths in a 
             DWDM line system that pass through the network, i.e. they are not sourced/terminated by the line-system operator’s 
             equipment (hence “alien”). This setup is in contrast to traditional DWDM systems, where the DWDM light source 
-            (transponder) operates in the same management domain as the amplifiers."
+            (transponder) operates in the same management domain as the amplifiers.
+
+            Where NRENs have given the number of individual alien wavelength services, the figure is available in a hover-over 
+            box. These are indicated by a black line around the coloured marker."
             category={Sections.Network} filter={filterNode}
             data={selectedData} filename="alien_wave_nrens_per_year">
             <>
@@ -62,18 +64,24 @@ function NetworkAlienWavePage(): React.ReactElement {
                             {Array.from(dataLookup.entries()).map(([nren, nrenMap]) => (
                                 <tr key={nren}>
                                     <td>{nren}</td>
-                                    {["yes", 
-                                    "planned", 
-                                    "no"].map(column_key => (
-                                        <td key={column_key}>
-                                            {nrenMap.has(column_key) &&
-                                                showYears.map(year => {
-                                                    const chargingYears = nrenMap.get(column_key)!;
-                                                    return <ColorPill key={year} year={year} active={chargingYears.has(year)} tooltip={""}/>;
-                                                })
-                                            }
-                                        </td>
-                                    ))}
+                                    {["yes",
+                                        "planned",
+                                        "no"].map(column_key => (
+                                            <td key={column_key}>
+                                                {nrenMap.has(column_key) &&
+                                                    showYears.map(year => {
+                                                        const chargingYears = nrenMap.get(column_key)!;
+                                                        const alienWaveInfo = chargingYears.get(year);
+                                                        return <ColorPill key={year} year={year} active={chargingYears.has(year)}
+                                                            tooltip={
+                                                                alienWaveInfo?.nr_of_alien_wave_third_party_services != null
+                                                                    ? `No. of Individual alien wavelength services = ${alienWaveInfo.nr_of_alien_wave_third_party_services}`
+                                                                    : ""
+                                                            } />;
+                                                    })
+                                                }
+                                            </td>
+                                        ))}
                                 </tr>
                             ))}
                         </tbody>
diff --git a/compendium-frontend/src/pages/NetworkTrafficVolume.tsx b/compendium-frontend/src/pages/NetworkTrafficVolume.tsx
index e2eaf735..8575f26b 100644
--- a/compendium-frontend/src/pages/NetworkTrafficVolume.tsx
+++ b/compendium-frontend/src/pages/NetworkTrafficVolume.tsx
@@ -53,7 +53,7 @@ const options = {
                 label: (context) => {
                     let tooltipLines: string[] = [];
                     const datasetLabel = context.dataset.label || '';
-                    const dataPoint = context.parsed.y;
+                    const dataPoint = `${context.parsed.y} TB `;
 
                     if (dataPoint !== null) {
                         tooltipLines.push(` ${dataPoint}   ${datasetLabel}`);
@@ -69,7 +69,7 @@ const options = {
         y: {
             ticks: {
                 callback: (value: string | number) => {
-                    return `${value}`;
+                    return `${value} TB`;
                 },
             },
         },
-- 
GitLab


From 8f003b3fca44e2e2b38ddf77b292a07ec162f7fd Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Sat, 26 Oct 2024 10:05:08 +0100
Subject: [PATCH 6/7] [COMP-365][COMP-366] Service level targets an mangement

---
 compendium-frontend/src/App.tsx               |  4 +
 compendium-frontend/src/Schema.tsx            |  5 ++
 .../src/components/PolicySidebar.tsx          | 10 +++
 .../src/pages/CompendiumData.tsx              | 10 +++
 .../src/pages/SNPServiceLevelTargets.tsx      | 77 +++++++++++++++++++
 .../pages/SNPServiceManagementFramework.tsx   | 77 +++++++++++++++++++
 .../src/scss/layout/_components.scss          |  3 +-
 .../routes/standards_and_policies.py          | 59 +++++++++++++-
 8 files changed, 241 insertions(+), 4 deletions(-)
 create mode 100644 compendium-frontend/src/pages/SNPServiceLevelTargets.tsx
 create mode 100644 compendium-frontend/src/pages/SNPServiceManagementFramework.tsx

diff --git a/compendium-frontend/src/App.tsx b/compendium-frontend/src/App.tsx
index cc6e5388..1065b9ed 100644
--- a/compendium-frontend/src/App.tsx
+++ b/compendium-frontend/src/App.tsx
@@ -50,6 +50,8 @@ import SurveyContainerComponent from "./survey/SurveyContainerComponent";
 import SurveyLanding from "./survey/Landing";
 import SNPServicesOfferedPage from "./pages/SNPServicesOffered";
 import SNPCorporateStrategyPage from "./pages/SNPCorporateStrategy";
+import SNPServiceManagementFrameworkPage from "./pages/SNPServiceManagementFramework";
+import SNPServiceLevelTargetsPage from "./pages/SNPServiceLevelTargets";
 
 
 const router = createBrowserRouter([
@@ -106,6 +108,8 @@ const router = createBrowserRouter([
   { path: "/crisis-exercise", element: <SNPStandardsCrisisExercisesPage /> },
   { path: "/security-control", element: <SNPStandardsSecurityControlsPage /> },
   { path: "/services-offered", element: <SNPServicesOfferedPage /> },
+  { path: "/service-management-framework", element: <SNPServiceManagementFrameworkPage /> },
+  { path: "/service-level-targets", element: <SNPServiceLevelTargetsPage /> },
   { path: "/corporate-strategy", element: <SNPCorporateStrategyPage /> },
   { path: "survey/admin/surveys", element: <SurveyManagementComponent /> },
   { path: "survey/admin/users", element: <UserManagementComponent /> },
diff --git a/compendium-frontend/src/Schema.tsx b/compendium-frontend/src/Schema.tsx
index 4377d24e..8a13cb3a 100644
--- a/compendium-frontend/src/Schema.tsx
+++ b/compendium-frontend/src/Schema.tsx
@@ -82,6 +82,11 @@ export interface AlienWaveInternal extends NrenAndYearDatapoint {
     alien_wave_internal: (string | null)
 }
 
+export interface ServiceManagement extends NrenAndYearDatapoint {
+    service_management_framework: (string | null),
+    service_level_targets: (string | null),
+}
+
 export interface Standards extends NrenAndYearDatapoint {
     audits: (string | null),
     audit_specifics: (string | null),
diff --git a/compendium-frontend/src/components/PolicySidebar.tsx b/compendium-frontend/src/components/PolicySidebar.tsx
index 34f55fa4..d162df63 100644
--- a/compendium-frontend/src/components/PolicySidebar.tsx
+++ b/compendium-frontend/src/components/PolicySidebar.tsx
@@ -48,6 +48,16 @@ const PolicySidebar = () => {
                     <span>NREN Corporate Strategies </span>
                 </Link>
             </Row>
+            <Row>
+                <Link to="/service-level-targets" className="link-text-underline">
+                    <span>NRENs Offering Service Level Targets</span>
+                </Link>
+            </Row>
+            <Row>
+                <Link to="/service-management-framework" className="link-text-underline">
+                    <span>NRENs Operating a Formal Service Management</span>
+                </Link>
+            </Row>
         </Sidebar>
     )
 }
diff --git a/compendium-frontend/src/pages/CompendiumData.tsx b/compendium-frontend/src/pages/CompendiumData.tsx
index 27d13fc5..8008801a 100644
--- a/compendium-frontend/src/pages/CompendiumData.tsx
+++ b/compendium-frontend/src/pages/CompendiumData.tsx
@@ -135,6 +135,16 @@ function CompendiumData(): ReactElement {
                                         <span>NREN Corporate Strategies </span>
                                     </Link>
                                 </Row>
+                                <Row>
+                                    <Link to="/service-level-targets" className="link-text-underline">
+                                        <span>NRENs Offering Service Level Targets</span>
+                                    </Link>
+                                </Row>
+                                <Row>
+                                    <Link to="/service-management-framework" className="link-text-underline">
+                                        <span>NRENs Operating a Formal Service Management</span>
+                                    </Link>
+                                </Row>
                             </div>
                         </CollapsibleBox>
 
diff --git a/compendium-frontend/src/pages/SNPServiceLevelTargets.tsx b/compendium-frontend/src/pages/SNPServiceLevelTargets.tsx
new file mode 100644
index 00000000..93ba6baf
--- /dev/null
+++ b/compendium-frontend/src/pages/SNPServiceLevelTargets.tsx
@@ -0,0 +1,77 @@
+import React, { useContext } from "react";
+import { Table } from "react-bootstrap";
+
+import { ServiceManagement } from "../Schema";
+import { createMatrixDataLookup } from "../helpers/dataconversion";
+import ColorPill from "../components/ColorPill";
+import DataPage from "../components/DataPage";
+import Filter from "../components/graphing/Filter";
+import { Sections } from "../helpers/constants";
+import { FilterSelectionContext } from "../providers/FilterSelectionProvider";
+import ChartContainer from "../components/graphing/ChartContainer";
+import { useData } from "../helpers/useData";
+
+function SNPServiceLevelTargetsPage(): React.ReactElement {
+    const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
+    const { data: pertTeamData, years, nrens } = useData<ServiceManagement>('/api/standards-and-policies/service-management', setFilterSelection);
+
+    const selectedData = (pertTeamData).filter(data =>
+        filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
+    );
+
+    const dataLookup = createMatrixDataLookup(selectedData, 'service_level_targets');
+    console.log(dataLookup);
+    const filterNode = <Filter
+        filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
+        filterSelection={filterSelection}
+        setFilterSelection={setFilterSelection}
+        coloredYears
+    />
+
+    const showYears = [...filterSelection.selectedYears.filter(year => years.has(year))].sort();
+
+    return (
+        <DataPage title="NRENs Offering Service Level Targets"
+            description="The table below shows which NRENs offer Service Levels Targets for their services. 
+            If NRENs have never responded to this question in the survey, they are excluded. "
+            category={Sections.Policy} filter={filterNode}
+            data={selectedData} filename="service_level_targets">
+            <>
+                <ChartContainer>
+                    <Table className="charging-struct-table" striped bordered >
+                        <colgroup>
+                            <col span={1} style={{ width: "30%" }} />
+                            <col span={1} style={{ width: "35%" }} />
+                            <col span={1} style={{ width: "35%" }} />
+                        </colgroup>
+                        <thead>
+                            <tr>
+                                <th></th>
+                                <th>Yes</th>
+                                <th>No</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {Array.from(dataLookup.entries()).map(([nren, nrenMap]) => (
+                                <tr key={nren}>
+                                    <td>{nren}</td>
+                                    {["True", "False"].map(column_key => (
+                                        <td key={column_key}>
+                                            {nrenMap.has(column_key) &&
+                                                showYears.map(year => {
+                                                    const chargingYears = nrenMap.get(column_key)!;
+                                                    return <ColorPill key={year} year={year} active={chargingYears.has(year)} tooltip={""} />;
+                                                })
+                                            }
+                                        </td>
+                                    ))}
+                                </tr>
+                            ))}
+                        </tbody>
+                    </Table>
+                </ChartContainer>
+            </>
+        </DataPage>
+    );
+}
+export default SNPServiceLevelTargetsPage;
diff --git a/compendium-frontend/src/pages/SNPServiceManagementFramework.tsx b/compendium-frontend/src/pages/SNPServiceManagementFramework.tsx
new file mode 100644
index 00000000..c82e7b5f
--- /dev/null
+++ b/compendium-frontend/src/pages/SNPServiceManagementFramework.tsx
@@ -0,0 +1,77 @@
+import React, { useContext } from "react";
+import { Table } from "react-bootstrap";
+
+import { ServiceManagement } from "../Schema";
+import { createMatrixDataLookup } from "../helpers/dataconversion";
+import ColorPill from "../components/ColorPill";
+import DataPage from "../components/DataPage";
+import Filter from "../components/graphing/Filter";
+import { Sections } from "../helpers/constants";
+import { FilterSelectionContext } from "../providers/FilterSelectionProvider";
+import ChartContainer from "../components/graphing/ChartContainer";
+import { useData } from "../helpers/useData";
+
+function SNPServiceManagementFrameworkPage(): React.ReactElement {
+    const { filterSelection, setFilterSelection } = useContext(FilterSelectionContext);
+    const { data: pertTeamData, years, nrens } = useData<ServiceManagement>('/api/standards-and-policies/service-management', setFilterSelection);
+
+    const selectedData = (pertTeamData).filter(data =>
+        filterSelection.selectedYears.includes(data.year) && filterSelection.selectedNrens.includes(data.nren)
+    );
+
+    const dataLookup = createMatrixDataLookup(selectedData, 'service_management_framework');
+
+    const filterNode = <Filter
+        filterOptions={{ availableYears: [...years], availableNrens: [...nrens.values()] }}
+        filterSelection={filterSelection}
+        setFilterSelection={setFilterSelection}
+        coloredYears
+    />
+
+    const showYears = [...filterSelection.selectedYears.filter(year => years.has(year))].sort();
+
+    return (
+        <DataPage title="NRENs Operating a Formal Service Management Framework"
+            description="The chart below shows which NRENs operate a formal service management framework 
+            for all of their services. NRENs which have never answered this question cannot be selected."
+            category={Sections.Policy} filter={filterNode}
+            data={selectedData} filename="service_management_framework">
+            <>
+                <ChartContainer>
+                    <Table className="charging-struct-table" striped bordered >
+                        <colgroup>
+                            <col span={1} style={{ width: "30%" }} />
+                            <col span={1} style={{ width: "35%" }} />
+                            <col span={1} style={{ width: "35%" }} />
+                        </colgroup>
+                        <thead>
+                            <tr>
+                                <th></th>
+                                <th>Yes</th>
+                                <th>No</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {Array.from(dataLookup.entries()).map(([nren, nrenMap]) => (
+                                <tr key={nren}>
+                                    <td>{nren}</td>
+                                    {["True", "False"].map(column_key => (
+                                        <td key={column_key}>
+                                            {nrenMap.has(column_key) &&
+                                                showYears.map(year => {
+                                                    const chargingYears = nrenMap.get(column_key)!;
+                                                    return <ColorPill key={year} year={year} active={chargingYears.has(year)} tooltip={""} />;
+                                                })
+                                            }
+                                        </td>
+                                    ))}
+                                </tr>
+                            ))}
+                        </tbody>
+                    </Table>
+                </ChartContainer>
+            </>
+        </DataPage>
+    );
+}
+export default SNPServiceManagementFrameworkPage;
diff --git a/compendium-frontend/src/scss/layout/_components.scss b/compendium-frontend/src/scss/layout/_components.scss
index 56d4d388..4deb0669 100644
--- a/compendium-frontend/src/scss/layout/_components.scss
+++ b/compendium-frontend/src/scss/layout/_components.scss
@@ -204,7 +204,8 @@ $year-colors: (
   #1E82B6,
   #13AC9C,
   #5454A8,
-  #8C6896
+  #8C6896,
+  #0069b0,
 );
 
 $service-tab-colors: (
diff --git a/compendium_v2/routes/standards_and_policies.py b/compendium_v2/routes/standards_and_policies.py
index 981ccecb..e265ef35 100644
--- a/compendium_v2/routes/standards_and_policies.py
+++ b/compendium_v2/routes/standards_and_policies.py
@@ -1,6 +1,7 @@
 from typing import Any
 
-from compendium_v2.db.presentation_models import Standards, CrisisExercises, SecurityControls, ServiceUserTypes
+from compendium_v2.db.presentation_models import (Standards, CrisisExercises, SecurityControls,
+                                                  ServiceUserTypes, ServiceManagement)
 from compendium_v2.routes import common
 from flask import Blueprint, jsonify
 
@@ -70,7 +71,7 @@ SECURITY_CONTROLS_RESPONSE_SCHEMA = {
 SERVICES_USER_TYPES = {
     '$schema': 'http://json-schema.org/draft-07/schema#',
     'definitions': {
-        'policy': {
+        'services_user_types': {
             'type': 'object',
             'properties': {
                 'nren': {'type': 'string'},
@@ -86,9 +87,31 @@ SERVICES_USER_TYPES = {
     },
 
     'type': 'array',
-    'items': {'$ref': '#/definitions/connected/proportion'}
+    'items': {'$ref': '#/definitions/services_user_types'}
 }
 
+SERVICE_MANAGEMENT = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'definitions': {
+        'service_management': {
+            'type': 'object',
+            'properties': {
+                'nren': {'type': 'string'},
+                'nren_country': {'type': 'string'},
+                'year': {'type': 'integer'},
+                'service_management_framework': {'type': 'string'},
+                'service_level_targets': {'type': 'string'},
+            },
+            'required': ['nren', 'nren_country', 'year', 'service_management_framework','service_level_targets'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/service_management'}
+}
+
+
 
 
 def standards_extract_data(standards: Standards) -> dict:
@@ -255,3 +278,33 @@ def service_user_types_view() -> Any:
 
     entries = [service_user_type_extract_data(entry) for entry in common.get_data(ServiceUserTypes)]
     return jsonify(entries)
+
+
+def service_management_extract_data(service_management: ServiceManagement):
+    return {
+        'nren': service_management.nren.name,
+        'nren_country': service_management.nren.country,
+        'year': service_management.year,
+        'service_management_framework': str(service_management.service_management_framework),
+        'service_level_targets': str(service_management.service_level_targets)
+    }
+
+
+@routes.route('/service-management', methods=['GET'])
+@common.require_accepts_json
+def service_management_view() -> Any:
+    """
+    handler for /api/standards-and-policies/service-management/ requests
+    returns policy information for each NREN/year combination
+
+    response will be formatted as:
+
+    .. asjson::
+        compendium_v2.routes.standards_and_policies.SERVICE_MANAGEMENT
+
+    :return:
+    """
+
+    entries = [service_management_extract_data(entry) for entry in common.get_data(ServiceManagement)]
+    return jsonify(entries)
+
-- 
GitLab


From 66983945687b15b24602f39a0bcbb7557fe05034 Mon Sep 17 00:00:00 2001
From: saket-geant <saket.agrahari@geant.org>
Date: Sat, 26 Oct 2024 10:17:02 +0100
Subject: [PATCH 7/7] tox and package json

---
 compendium-frontend/package-lock.json          | 13 +++++++------
 compendium-frontend/package.json               |  2 +-
 compendium_v2/routes/standards_and_policies.py |  5 +----
 3 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/compendium-frontend/package-lock.json b/compendium-frontend/package-lock.json
index 26777645..1b535ab4 100644
--- a/compendium-frontend/package-lock.json
+++ b/compendium-frontend/package-lock.json
@@ -6843,10 +6843,11 @@
       }
     },
     "node_modules/mini-css-extract-plugin": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz",
-      "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==",
+      "version": "2.9.1",
+      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz",
+      "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "schema-utils": "^4.0.0",
         "tapable": "^2.2.1"
@@ -14768,9 +14769,9 @@
       "dev": true
     },
     "mini-css-extract-plugin": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz",
-      "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==",
+      "version": "2.9.1",
+      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz",
+      "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==",
       "dev": true,
       "requires": {
         "schema-utils": "^4.0.0",
diff --git a/compendium-frontend/package.json b/compendium-frontend/package.json
index d8d23656..bd2d7d1f 100644
--- a/compendium-frontend/package.json
+++ b/compendium-frontend/package.json
@@ -47,10 +47,10 @@
     "react-bootstrap": "~2.10.4",
     "react-chartjs-2": "~5.2.0",
     "react-dom": "~18.3.1",
+    "react-hot-toast": "~2.4.1",
     "react-icons": "~5.2.1",
     "react-router-dom": "~6.24.1",
     "survey-react-ui": "~1.11.5",
-    "react-hot-toast": "~2.4.1",
     "xlsx": "~0.18.5"
   },
   "description": "## development environment",
diff --git a/compendium_v2/routes/standards_and_policies.py b/compendium_v2/routes/standards_and_policies.py
index e265ef35..686d46ae 100644
--- a/compendium_v2/routes/standards_and_policies.py
+++ b/compendium_v2/routes/standards_and_policies.py
@@ -102,7 +102,7 @@ SERVICE_MANAGEMENT = {
                 'service_management_framework': {'type': 'string'},
                 'service_level_targets': {'type': 'string'},
             },
-            'required': ['nren', 'nren_country', 'year', 'service_management_framework','service_level_targets'],
+            'required': ['nren', 'nren_country', 'year', 'service_management_framework', 'service_level_targets'],
             'additionalProperties': False
         }
     },
@@ -112,8 +112,6 @@ SERVICE_MANAGEMENT = {
 }
 
 
-
-
 def standards_extract_data(standards: Standards) -> dict:
     return {
         'nren': standards.nren.name,
@@ -307,4 +305,3 @@ def service_management_view() -> Any:
 
     entries = [service_management_extract_data(entry) for entry in common.get_data(ServiceManagement)]
     return jsonify(entries)
-
-- 
GitLab