diff --git a/templates/childrensuspectwarning.py b/templates/childrensuspectwarning.py
new file mode 100644
index 0000000..cd7e758
--- /dev/null
+++ b/templates/childrensuspectwarning.py
@@ -0,0 +1,82 @@
+from ast import Str
+from collections.abc import Callable
+from xmlrpc.client import Boolean
+from valispace import API
+import warnings
+import time
+
+VALISPACE = {
+ 'domain': 'https://demonstration.valispace.com/',
+ 'username': 'AutomationsAPI',
+ 'password': 'AutomationsAPI'
+}
+
+DEFAULT_VALUES = {
+ "project": 24,
+}
+
+
+def get_map(api: API, endpoint: Str = "/", name: Str = "id", name_map_func: Callable[[Str], Boolean] = None, filter_func: Callable[[Str], Boolean]= None):
+ """
+ Function that given an endpoint returns a dict with specific keys.
+ If function is provided it generates the key. name_map_func must receive an object instance.
+ Otherwise key will be the property of each object specified in name.
+ """
+ mapping = {}
+ if not name_map_func:
+ name_map_func = lambda x: x[name]
+ for inst in api.get(endpoint):
+ if filter_func and not filter_func(inst):
+ # Filtered out
+ continue
+
+ key = name_map_func(inst)
+ if not mapping.get(key):
+ mapping[key] = inst
+ else:
+ warnings.warn(f"Warning ---> Key: {key} already has an object. Some data may be lost in mapping.")
+ return mapping
+
+
+def main(**kwargs):
+
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password'),
+ warn_https = VALISPACE.get('warn_https', False),
+ )
+
+ all_deployment_users = get_map(api, f"user/", "id")
+
+ requirement_data = kwargs['triggered_objects'][0]
+
+ #requirement_data = api.get('requirements/81') #just for testing now without automation to receive the trigger object
+ print(requirement_data)
+ time.sleep(15)
+
+ user_changing_req = all_deployment_users[requirement_data['updated_by']]
+ username = user_changing_req['first_name']+" "+user_changing_req['last_name']
+ user = f"@{username}"
+ requirement = f"${requirement_data['identifier']}"
+
+ print(requirement_data["children"])
+ req_children = requirement_data["children"]
+ if len(req_children)>0:
+ for children in req_children:
+
+ discussionData = {
+ "title" : user + " -> Parent of "+ requirement + " was updated",
+ "project": DEFAULT_VALUES["project"],
+ "content_type" : 120,
+ "group": 1,
+ "object_id": children,
+ }
+ createdDiscussion = api.post('discussions/', data=discussionData)
+
+ print("Discussions created for each Children")
+
+ pass
+
+if __name__=='__main__':
+ main()
diff --git a/templates/newtask.py b/templates/newtask.py
new file mode 100644
index 0000000..820b526
--- /dev/null
+++ b/templates/newtask.py
@@ -0,0 +1,50 @@
+from valispace import API
+from datetime import datetime, timedelta
+
+
+
+VALISPACE = {
+ 'domain': 'https://demonstration.valispace.com/',
+ 'username': 'AutomationsAPI',
+ 'password': 'AutomationsAPI'
+}
+
+DEFAULT_VALUES = {
+ "project": 24,
+ "start_date": "",
+ "today_date": ""
+}
+
+def main(**kwargs):
+
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password')
+ )
+ DEFAULT_VALUES["start_date"] = api.get('project/'+str(DEFAULT_VALUES["project"]))['start_date']
+ DEFAULT_VALUES["today_date"] = datetime.now().strftime("%Y-%m-%d")
+ print (DEFAULT_VALUES["today_date"])
+ print(kwargs)
+
+ #object_id = triggers[0]['id']
+
+ taskData = {
+ "title" : "testing Task Creation",
+ "project": DEFAULT_VALUES["project"],
+ "description" : "this is the description of the testing tasks",
+ "duration" : 5,
+ "duration_unit" : "days",
+ "start_date" : DEFAULT_VALUES["today_date"],
+ "content_type" : 38,
+ #"object_id": object_id,
+ "object_id": 13700,
+ "public" : True
+ }
+ createdTask = api.post('user-tasks/', data=taskData)
+ updatedTask = api.request('PUT', 'user-tasks/'+str(createdTask['id'])+"/add-member/", data={"member": "5"})
+
+ pass
+
+if __name__=='__main__':
+ main()
diff --git a/templates/requirementstats.py b/templates/requirementstats.py
new file mode 100644
index 0000000..5847cd2
--- /dev/null
+++ b/templates/requirementstats.py
@@ -0,0 +1,124 @@
+import csv, json
+from valispace import API
+
+import warnings
+import os
+import urllib.request
+import time
+
+from ast import Str
+from collections.abc import Callable
+from xmlrpc.client import Boolean
+
+VALISPACE = {
+ 'domain': 'https://demonstration.valispace.com/',
+ 'username': 'AutomationsAPI',
+ 'password': 'AutomationsAPI'
+}
+
+DEFAULT_VALUES = {
+ "project": 24,
+}
+
+def get_map(api: API, endpoint: Str = "/", name: Str = "id", name_map_func: Callable[[Str], Boolean] = None, filter_func: Callable[[Str], Boolean]= None):
+ """
+ Function that given an endpoint returns a dict with specific keys.
+ If function is provided it generates the key. name_map_func must receive an object instance.
+ Otherwise key will be the property of each object specified in name.
+ """
+ mapping = {}
+ if not name_map_func:
+ name_map_func = lambda x: x[name]
+ for inst in api.get(endpoint):
+ if filter_func and not filter_func(inst):
+ # Filtered out
+ continue
+
+ key = name_map_func(inst)
+ if not mapping.get(key):
+ mapping[key] = inst
+ else:
+ warnings.warn(f"Warning ---> Key: {key} already has an object. Some data may be lost in mapping.")
+ return mapping
+
+def main(**kwargs):
+
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password')
+ )
+
+ all_project_requirements = get_map(api, f"requirements/complete/?project="+str(DEFAULT_VALUES["project"]), "id")
+
+ nr_reqs = 0 #15093
+ nr_req_w_children = 0 #15094
+ nr_req_w_vm = 0 #15095
+ nr_req_vm_analysis = 0 #15099
+ nr_req_vm_review = 0 #15100
+ nr_req_vm_inspection = 0 #15101
+ nr_req_vm_rules = 0 #15102
+ nr_req_vm_tests = 0 #15103
+ nr_req_state_draft = 0 #15096
+ nr_req_state_in_review = 0 #15097
+ nr_req_state_final = 0 #15098
+ nr_req_verified = 0 #15104
+ nr_req_not_verified = 0 #15105
+
+ nr_reqs = len(all_project_requirements)
+
+ for requirement in all_project_requirements:
+ req_data=all_project_requirements[requirement]
+
+ if req_data['verified'] == True:
+ nr_req_verified += 1
+ elif req_data['verified'] == False:
+ nr_req_not_verified += 1
+
+ if req_data['total_children'] != 0:
+ nr_req_w_children += 1
+
+ if req_data['state'] == 7: #but for other projects we need to get by state name
+ nr_req_state_draft += 1
+ elif req_data['state'] == 8:
+ nr_req_state_in_review += 1
+ elif req_data['state'] == 9:
+ nr_req_state_final += 1
+
+ if req_data['verification_methods'] != None:
+ nr_req_w_vm += 1
+ for vm in req_data['verification_methods'] :
+ if vm['method'] != None:
+ vm_name = vm['method']['name']
+ if vm_name == "Analysis":
+ nr_req_vm_analysis += 1
+ elif vm_name == "Inspection":
+ nr_req_vm_inspection += 1
+ elif vm_name == "Review":
+ nr_req_vm_review += 1
+ elif vm_name == "Rules":
+ nr_req_vm_rules += 1
+ elif vm_name == "Test":
+ nr_req_vm_tests += 1
+
+ print("going to patch")
+ time.sleep(15)
+
+ api.request("patch", "valis/15093/", data={"formula":nr_reqs})
+ api.request("patch", "valis/15094/", data={"formula":nr_req_w_children})
+ api.request("patch", "valis/15095/", data={"formula":nr_req_w_vm})
+ api.request("patch", "valis/15096/", data={"formula":nr_req_state_draft})
+ api.request("patch", "valis/15097/", data={"formula":nr_req_state_in_review})
+ api.request("patch", "valis/15098/", data={"formula":nr_req_state_final})
+ api.request("patch", "valis/15099/", data={"formula":nr_req_vm_analysis})
+ api.request("patch", "valis/15100/", data={"formula":nr_req_vm_review})
+ api.request("patch", "valis/15101/", data={"formula":nr_req_vm_inspection})
+ api.request("patch", "valis/15102/", data={"formula":nr_req_vm_rules})
+ api.request("patch", "valis/15103/", data={"formula":nr_req_vm_tests})
+ api.request("patch", "valis/15104/", data={"formula":nr_req_verified})
+ api.request("patch", "valis/15105/", data={"formula":nr_req_not_verified})
+
+ pass
+
+if __name__=='__main__':
+ main()
diff --git a/templates/simpleconnectiontest.py b/templates/simpleconnectiontest.py
new file mode 100644
index 0000000..718017b
--- /dev/null
+++ b/templates/simpleconnectiontest.py
@@ -0,0 +1,34 @@
+from valispace import API
+from datetime import datetime, timedelta
+import time
+
+VALISPACE = {
+ 'domain': 'https://demonstration.valispace.com/',
+ 'username': 'AutomationsAPI',
+ 'password': 'AutomationsAPI'
+}
+
+DEFAULT_VALUES = {
+ "project": 24,
+ "start_date": "",
+ "today_date": ""
+}
+
+def main(**kwargs):
+
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password')
+ #warn_https=False
+ )
+ DEFAULT_VALUES["start_date"] = api.get('project/'+str(DEFAULT_VALUES["project"]))['start_date']
+ DEFAULT_VALUES["today_date"] = datetime.now().strftime("%Y-%m-%d")
+ print (DEFAULT_VALUES["today_date"])
+ time.sleep(60)
+ print(kwargs)
+
+ pass
+
+if __name__=='__main__':
+ main()
\ No newline at end of file
diff --git a/templates/simulations/electronicpressureregulator.m b/templates/simulations/electronicpressureregulator.m
new file mode 100644
index 0000000..0e25dd2
--- /dev/null
+++ b/templates/simulations/electronicpressureregulator.m
@@ -0,0 +1,41 @@
+function [response] = main(data)
+ %% This is the first function to be called when executing the script
+ % -----------------------------------------------------------------
+ % Data: Scalar structure with data comming from Valispace.
+ %
+ % Example of use:
+ % mass = data.mass
+ % ------------------------------------------------------------------
+ % Response: Scalar structure with data to send to Valispace.
+ %
+ % Example of use:
+ % response = struct()
+ % response.total_mass = data.mass * 10
+ % response.double_mass = data.mass * 2
+ % ------------------------------------------------------------------
+
+ % Get inputs from Valispace
+ P_in = data.P_in;
+ V_in = data.V_in;
+
+ % Previous code from simulation
+ V_in = 1.2 ;
+ F_1 = 25 ;
+ PV_1 = P_in*V_in;
+ m_dot = 6;
+ density = 5.761;
+ A_inlet = m_dot / (density*V_in);
+ A_2 = A_inlet/2.5;
+ m_dot2 = 5;
+ V_2 = m_dot2/(density*A_2);
+ P_2 = PV_1/V_2;
+ A_orifice = F_1/P_2;
+ PV_2 = P_2*V_2;
+ V_out = 70.58882;
+ Pout = PV_2/V_out;
+
+ % Send outputs back to Valispace
+ response = struct()
+ response.Pout = Pout;
+
+end
diff --git a/templates/simulations/electronicpressureregulator.py b/templates/simulations/electronicpressureregulator.py
new file mode 100644
index 0000000..cd32310
--- /dev/null
+++ b/templates/simulations/electronicpressureregulator.py
@@ -0,0 +1,25 @@
+from pathlib import Path
+from typing import Dict, Any
+import oct2py # pylint: disable=import-error
+
+
+def main(**kwargs) -> Dict[str, Any]:
+ """
+ This is the main function to execute your script and it must exists.
+ You shouldn't modify this function unless you are familiar with oct2py.
+
+ Other functions and files can be also created. You have at your disposal
+ to import Valispace API, oct2py, scipy, numpy and pint.
+
+ :param kwargs: Dictionary with data received from Valispace.
+ :type kwargs: Dict[str, Any]
+
+ :return: Dictionary with data to send back to Valispace.
+ :rtype: Dict[str, Any]
+ """
+ # Initialize octave and add all .m files to octave
+ octave = oct2py.Oct2Py()
+ octave.addpath(str(Path(__file__).resolve().parent))
+
+ # Run main function from main.m and send response back to Valispace
+ return octave.main(kwargs)
diff --git a/templates/simulations/idealforcecoefficient.m b/templates/simulations/idealforcecoefficient.m
new file mode 100644
index 0000000..d2c0101
--- /dev/null
+++ b/templates/simulations/idealforcecoefficient.m
@@ -0,0 +1,54 @@
+function [response] = main(data)
+ %% This is the first function to be called when executing the script
+ % -----------------------------------------------------------------
+ % Data: Scalar structure with data comming from Valispace.
+ %
+ % Example of use:
+ % mass = data.mass
+ % ------------------------------------------------------------------
+ % Response: Scalar structure with data to send to Valispace.
+ %
+ % Example of use:
+ % response = struct()
+ % response.total_mass = data.mass * 10
+ % response.double_mass = data.mass * 2
+ % ------------------------------------------------------------------
+
+ % Get inputs from Valispace
+ g = data.g;
+ SIGMA = data.SIGMA;
+
+ % Previous code from simulation
+ k = 1e-5; %initial guess
+ Gamma = sqrt(g)*(2/(g+1))^((g+1)/(2*g-2));
+ a = 2*g/(g-1);
+ b = 2/g;
+ c = (g+1)/g;
+ f = Gamma/sqrt(a*(k^b-k^c)) - SIGMA; %f(x)
+ df = a*(c*Gamma*k^c-b*Gamma*k^b)/(2*k*(a*(k^b-k^c))^(3/2)); %f'(x)
+ k_next = k-f/df; %Next guess
+ difference = abs(k_next-k)/k;
+ counter = 0;
+
+ while difference > k*1e-2
+
+ k = k_next;
+ f = Gamma/sqrt(a*(k^b-k^c)) - SIGMA; %f(x)
+
+ df = a*(c*Gamma*k^c-b*Gamma*k^b)/(2*k*(a*(k^b-k^c))^(3/2)); %f'(x)
+ k_next = k-f/df;%Next guess
+ difference = abs(k_next-k);
+ counter = counter + 1;
+
+ endwhile
+
+ C_F = Gamma*sqrt(a*(1-k^((g-1)/g)))+SIGMA*(k);
+ k = k_next;
+
+ % Send outputs back to Valispace
+ response = struct()
+ response.C_F = C_F;
+ response.k = k;
+
+end
+
\ No newline at end of file
diff --git a/templates/simulations/idealforcecoefficient.py b/templates/simulations/idealforcecoefficient.py
new file mode 100644
index 0000000..cd32310
--- /dev/null
+++ b/templates/simulations/idealforcecoefficient.py
@@ -0,0 +1,25 @@
+from pathlib import Path
+from typing import Dict, Any
+import oct2py # pylint: disable=import-error
+
+
+def main(**kwargs) -> Dict[str, Any]:
+ """
+ This is the main function to execute your script and it must exists.
+ You shouldn't modify this function unless you are familiar with oct2py.
+
+ Other functions and files can be also created. You have at your disposal
+ to import Valispace API, oct2py, scipy, numpy and pint.
+
+ :param kwargs: Dictionary with data received from Valispace.
+ :type kwargs: Dict[str, Any]
+
+ :return: Dictionary with data to send back to Valispace.
+ :rtype: Dict[str, Any]
+ """
+ # Initialize octave and add all .m files to octave
+ octave = oct2py.Oct2Py()
+ octave.addpath(str(Path(__file__).resolve().parent))
+
+ # Run main function from main.m and send response back to Valispace
+ return octave.main(kwargs)
diff --git a/templates/specificationgeneration.py b/templates/specificationgeneration.py
new file mode 100644
index 0000000..7d28813
--- /dev/null
+++ b/templates/specificationgeneration.py
@@ -0,0 +1,548 @@
+from typing import Any, Dict
+import operator
+import warnings
+import os
+import getpass
+import requests
+
+from ast import Str
+from collections.abc import Callable
+from xmlrpc.client import Boolean
+from valispace import API
+from PIL import Image
+from docx import Document
+from docx.shared import Inches
+from mailmerge import MailMerge
+from natsort import natsorted
+from io import BytesIO
+
+
+import urllib.request
+
+from htmldocx import HtmlToDocx
+
+#TEMPLATE_FILES_DIRECROTY = os.environ['USERPROFILE']+'/Documents/Generic Specification Template.docx'
+
+CURRENT_SPECIFICATION = {
+ "id": 0,
+ "name": "",
+ "section": 0,
+}
+
+VALISPACE = {
+ 'domain': 'https://demonstration.valispace.com/',
+ 'username': 'AutomationsAPI',
+ 'password': 'AutomationsAPI'
+}
+
+DEFAULT_VALUES = {
+ "project": 24
+}
+
+def keep_tables_on_one_page(doc):
+ tags = doc.element.xpath('//w:tr[position() < last()]/w:tc/w:p')
+ for tag in tags:
+ ppr = tag.get_or_add_pPr()
+ ppr.keepNext_val = True
+
+def remove_all_empty_headings(document):
+ for paragraph in document.paragraphs:
+ if (paragraph.style.name == "Heading 1" or paragraph.style.name == "Heading 2") and paragraph.text =='':
+ p = paragraph._element
+ p.getparent().remove(p)
+ p._p = p._element = None
+
+def remove_all_but_last_section(document):
+ sectPrs = document._element.xpath(".//w:pPr/w:sectPr")
+ for sectPr in sectPrs:
+ sectPr.getparent().remove(sectPr)
+ return
+
+def get_requirements_without_section(all_spec_requirements):
+ unsorted_section_requirements = {}
+ no_section_requirements = {}
+ for requirement in all_spec_requirements:
+ if all_spec_requirements[requirement]['group'] == None:
+ unsorted_section_requirements[requirement] = all_spec_requirements[requirement]
+
+ sorted_requirements = natsorted(unsorted_section_requirements.items(), key=lambda x:(operator.getitem(x[1],'identifier').replace(". "," ")))
+
+ for sorted_ in sorted_requirements:
+ no_section_requirements[sorted_[0]] = sorted_[1]
+
+ return no_section_requirements
+
+def get_section_requirements(all_specification_requirements,section):
+ unsorted_section_requirements = {}
+ section_requirements = {}
+ for requirement in all_specification_requirements:
+ if all_specification_requirements[requirement]['group'] == section:
+ unsorted_section_requirements[requirement] = all_specification_requirements[requirement]
+
+ sorted_requirements_by_section = natsorted(unsorted_section_requirements.items(), key=lambda x:(operator.getitem(x[1],'identifier').replace(". "," ")))
+
+ for sorted_ in sorted_requirements_by_section:
+ section_requirements[sorted_[0]] = sorted_[1]
+
+ return section_requirements
+
+def get_specification_sections(api, project):
+ specification_sections = {}
+ all_specifications_groups = get_map(api, f"requirements/groups/?project={project}", "id", None, filter_specification)
+ pre_sorted_section_groups = natsorted(all_specifications_groups.items(), key=lambda x:(operator.getitem(x[1],'name').replace(". "," ")))
+
+ for pre_sorted in pre_sorted_section_groups:
+ specification_sections[pre_sorted[0]] = pre_sorted[1]
+
+ return specification_sections
+
+def put_images(document, all_project_images):
+
+ for paragraph in document.paragraphs:
+ if "Images_Placeholder" in paragraph.text:
+ #still needs testing/refined. Currently only working/tested for tables!
+ req_images = get_requirement_images(requirement_id,all_project_images)
+ for image in req_images:
+ image_data = Image.open(requests.get(req_images[image]['download_url'], stream=True).raw)
+ run = paragraph.add_run()
+ run.add_picture(image_data.fp, height=Inches(2.0))
+ for run in reversed(list(paragraph.runs)[:-len(req_images)]):
+ paragraph._p.remove(run._r)
+ elif "No_Images" in paragraph.text:
+ p_el = paragraph._element
+ p_el.getparent().remove(p_el)
+ paragraph._p = paragraph._element = None
+
+ for tables in document.tables:
+ for celda in tables._cells:
+ hasImages = False
+ requirement_id = 0
+ for p in celda.paragraphs:
+ if "Images_Placeholder" in p.text:
+ requirement_id = int(p.text[19:])
+ hasImages = True
+ p_el = p._element
+ p_el.getparent().remove(p_el)
+ p._p = p._element = None
+ elif "No_Images" in p.text:
+ p_el = p._element
+ p_el.getparent().remove(p_el)
+ p._p = p._element = None
+ if hasImages:
+ new_image_p = celda.add_paragraph()
+ req_images = get_requirement_images(requirement_id,all_project_images)
+ for image in req_images:
+ image_data = Image.open(requests.get(req_images[image]['download_url'], stream=True).raw)
+ run = new_image_p.add_run()
+ run.add_picture(image_data.fp, height=Inches(2.0))
+ for run in reversed(list(new_image_p.runs)[:-len(req_images)]):
+ new_image_p._p.remove(run._r)
+
+def clone_run_props(tmpl_run, this_run):
+ this_run.bold = tmpl_run.bold
+ this_run.italic = tmpl_run.italic
+ this_run.underline = tmpl_run.underline
+ this_run.font.color.rgb = tmpl_run.font.color.rgb
+ this_run.font.highlight_color = tmpl_run.font.highlight_color
+ this_run.font.strike = tmpl_run.font.strike
+
+def put_html_text(document, docx_list):
+ #We copy all the runs (with format) of the temporary docx with requirement text formated from html conversion into the main document
+ for docx_html_id in docx_list:
+ for tables in document.tables:
+ for celda in tables._cells:
+ for p in celda.paragraphs:
+ breakfor = False
+ if docx_html_id in p.text:
+ for html_paragraph in docx_list[docx_html_id].paragraphs:
+ if html_paragraph.style.name == 'List Bullet':
+ new_paragraph = celda.add_paragraph(style='BulletList')
+ elif html_paragraph.style.name == 'List Number':
+ new_paragraph = celda.add_paragraph(style='NumberList')
+ else: new_paragraph = celda.add_paragraph(style='No Spacing')
+ for run in html_paragraph.runs:
+ if run.text != '':
+ cloned_run = new_paragraph.add_run()
+ clone_run_props(run, cloned_run)
+ cloned_run.text = run.text
+ breakfor = True
+ #nexet we remove the placeholders
+ p_el = p._element
+ p_el.getparent().remove(p_el)
+ p._p = p._element = None
+ if breakfor:
+ break
+ if breakfor:
+ break
+
+def filter_specification(element):
+ if element['specification'] == CURRENT_SPECIFICATION["id"]:
+ return True
+ return False
+
+def filter_images(file):
+ if file['mimetype'] == 'image/jpeg' or file['mimetype'] == 'image/png':
+ return True
+ return False
+
+def filter_not_images(file):
+ if file['mimetype'] != 'image/jpeg' and file['mimetype'] != 'image/png':
+ return True
+ return False
+
+def has_images(requirement,images):
+ for objectid in images:
+ if images[objectid]['object_id'] == requirement:
+ return True
+ return False
+
+def get_requirement_images(requirement,images):
+ requirement_images = {}
+ for image in images:
+ if images[image]['object_id'] == requirement:
+ requirement_images[image] = images[image]
+ return requirement_images
+
+def get_requirement_applicability(requirement,all_project_component_types, all_project_applicability_conditions):
+ all_requirement_applicabilities = ""
+
+ for applicability in all_project_applicability_conditions:
+ if all_project_applicability_conditions[applicability]['requirement'] == requirement:
+ counter = 0
+ for component_types in all_project_applicability_conditions[applicability]['component_types']:
+ if len(all_project_applicability_conditions[applicability]['component_types']) == 1:
+ all_requirement_applicabilities+=all_project_component_types.get(component_types)['name']
+ elif counter == 0:
+ all_requirement_applicabilities+=all_project_component_types.get(component_types)['name']
+ else: all_requirement_applicabilities+="|"+all_project_component_types.get(component_types)['name']
+ counter+= 1
+ all_requirement_applicabilities+=" ; "
+ if (len(all_requirement_applicabilities)) > 1:
+ all_requirement_applicabilities = all_requirement_applicabilities[:-3]
+ return all_requirement_applicabilities
+
+def get_requirement_verification_methods(verification_methods):
+ requirement_vms = ""
+ if verification_methods == None:
+ return requirement_vms
+ else:
+ for vm in verification_methods:
+ if vm['method'] != None:
+ requirement_vms+= vm['method']['name']
+ requirement_vms+=" ; "
+ if (len(verification_methods)) > 0:
+ requirement_vms = requirement_vms[:-3]
+ return requirement_vms
+
+def get_requirement_verification_methods_newline(verification_methods):
+ requirement_vms = ""
+ if verification_methods == None:
+ return requirement_vms
+ else:
+ for vm in verification_methods:
+ if vm['method'] != None:
+ requirement_vms+= vm['method']['name']
+ requirement_vms+=" \n\n "
+ if (len(verification_methods)) > 0:
+ requirement_vms = requirement_vms[:-3]
+ return requirement_vms
+
+def get_requirement_verification_methods_comments(verification_methods):
+ requirement_vms = ""
+ if verification_methods == None:
+ return requirement_vms
+ else:
+ for vm in verification_methods:
+ if vm['component_vms'] != None:
+ for component_vms in vm['component_vms']:
+ if component_vms['comment'] != None:
+ requirement_vms+= component_vms['comment'][3:-4] #hardcode remove of
and
while clean_text is not available for this field
+ requirement_vms+=" \n\n "
+ if (len(verification_methods)) > 0:
+ requirement_vms = requirement_vms[:-3]
+ return requirement_vms
+
+def get_requirement_verification_methods_text(verification_methods):
+ requirement_vms_text = ""
+ if verification_methods == None:
+ return requirement_vms_text
+ else:
+ for vm in verification_methods:
+ if vm['text'] != None:
+ requirement_vms_text+= vm['text'][3:-4] #hardcode remove of and
while clean_text is not available for this field
+ if (len(verification_methods)) > 0:
+ requirement_vms_text = requirement_vms_text[:-3]
+ return requirement_vms_text
+
+def get_requirement_verification_methods_and_text(requirement,all_project_requirement_vms,all_project_requirement_ver_methods):
+ requirement_vms = ""
+ for req_vms_id in list(all_project_requirement_vms):
+ req_vms = all_project_requirement_vms[req_vms_id]
+ if req_vms['requirement'] == requirement:
+ if req_vms['text'] != None:
+ vm_method_and_text = req_vms['text'][:3] + all_project_requirement_ver_methods[req_vms['method']]['name'] +': ' + req_vms['text'][3:]
+ requirement_vms+= vm_method_and_text
+ all_project_requirement_vms.pop(req_vms_id)
+ return requirement_vms
+
+
+def get_requirement_attachments_references(requirement,files):
+ requirement_attachments = ""
+ for file in files:
+ if files[file]['object_id'] == requirement:
+ if files[file]['reference'] != None:
+ requirement_attachments+= files[file]['name']+" - "+files[file]['reference'] #" - "+files[file]['version'] #for the future when we also upload the file versiopn and not controled by VS
+ else :
+ requirement_attachments+= files[file]['name'] #+" - "+files[file]['reference'] #" - "+files[file]['version'] #for the future when we also upload the file versiopn and not controled by VS
+ requirement_attachments+=" ; "
+ if (len(requirement_attachments)) > 0:
+ requirement_attachments = requirement_attachments[:-3]
+ return requirement_attachments
+
+def get_requirement_type(requirement,all_project_requirement_types):
+ for req_types in all_project_requirement_types:
+ if all_project_requirement_types[req_types]['id'] == requirement['type']:
+ return all_project_requirement_types[req_types]['name']
+ return ""
+
+def get_requirement_owner(ownerId,all_deployment_users, all_deployment_user_groups):
+ #It will get the User groups (removig Internal and External) + the user First and Last Names
+ #needs to be fixed!
+ req_owner = ""
+ if ownerId == None:
+ return req_owner
+ else: req_owner_id = ownerId['id']
+
+ req_owner = all_deployment_users[req_owner_id]['first_name']+ " " + all_deployment_users[req_owner_id]['last_name']
+ req_owner_groups = ""
+
+ if len(all_deployment_users[req_owner_id]['groups'])>0:
+ for user_groups in all_deployment_users[req_owner_id]['groups']:
+ if all_deployment_user_groups[user_groups]['name'] != "Internal" and all_deployment_user_groups[user_groups]['name'] != "External":
+ req_owner_groups+= all_deployment_user_groups[user_groups]['name']
+ req_owner_groups+=" / "
+ if (len(req_owner_groups)) > 0:
+ req_owner_groups = req_owner_groups[:-3]
+ req_owner = req_owner_groups + " -> " + req_owner
+
+ return req_owner
+
+def get_requirement_custom_field(requirement,all_project_custom_fields,all_project_custom_field_options,field_name):
+
+ custom_field_value = ""
+ req_custom_fields = requirement['custom_values']
+ if len(req_custom_fields) > 0 :
+ for custom_field in req_custom_fields:
+ int_custom_field = int(custom_field)
+ if all_project_custom_fields[int_custom_field]['name'] == field_name:
+ if all_project_custom_fields[int_custom_field]['type'] == 0:
+ custom_field_value = req_custom_fields[custom_field]
+ custom_field_value = custom_field_value[3:-4] #to remove the and
+ elif all_project_custom_fields[int_custom_field]['type'] == 1:
+ custom_field_value = all_project_custom_field_options[req_custom_fields[custom_field]]['name']
+ return custom_field_value
+
+def get_map(api: API, endpoint: Str = "/", name: Str = "id", name_map_func: Callable[[Str], Boolean] = None, filter_func: Callable[[Str], Boolean]= None):
+ """
+ Function that given an endpoint returns a dict with specific keys.
+ If function is provided it generates the key. name_map_func must receive an object instance.
+ Otherwise key will be the property of each object specified in name.
+ """
+ mapping = {}
+ if not name_map_func:
+ name_map_func = lambda x: x[name]
+ for inst in api.get(endpoint):
+ if filter_func and not filter_func(inst):
+ # Filtered out
+ continue
+
+ key = name_map_func(inst)
+ if not mapping.get(key):
+ mapping[key] = inst
+ else:
+ warnings.warn(f"Warning ---> Key: {key} already has an object. Some data may be lost in mapping.")
+ return mapping
+
+def get_specification_requirements(all_project_requirements):
+ spec_requirements = {}
+ for requirement in all_project_requirements:
+ if all_project_requirements[requirement]['specification'] == CURRENT_SPECIFICATION["id"]:
+ spec_requirements[requirement] = all_project_requirements[requirement]
+ return spec_requirements
+
+def create_file(api, file_name, file_data, project_id: int, obj=None):
+
+ del api._session.headers['Content-Type']
+ result = api._session.request("POST", api._url + "files/", files={
+ 'file': (file_name, file_data),
+ }, data={
+ 'project': project_id,
+ # 'folder': DEFAULT_VALUES['specs_folder'],
+ 'content_type': obj['contenttype'] if obj else 13,
+ 'object_id': obj['id'] if obj else project_id,
+ })
+ try:
+ result.raise_for_status()
+ new_file = result.json()
+ except:
+ new_file = None
+ api._session.headers['Content-Type'] = "application/json"
+ return new_file
+
+def create_specification_document(api, document,all_project_requirements, specification_data, all_project_images, all_project_component_types, all_project_applicability_conditions,all_project_requirement_types,all_project_requirement_vms,all_project_requirement_ver_methods):
+
+ print ("Going to create Specification document for -> "+ specification_data['name'])
+ OUTPUT_FILE = specification_data['name']+'.docx'
+ CURRENT_SPECIFICATION["id"] = specification_data['id']
+
+ template_data = []
+
+ all_specification_requirements = get_specification_requirements(all_project_requirements)
+
+ if len(all_specification_requirements) <1:
+ print("No requirements for Specification -> "+ specification_data['name'])
+ return
+
+
+ CURRENT_SPECIFICATION["name"] = specification_data['name']
+ new_parser = HtmlToDocx()
+ docx_list = {}
+ #1st we will add requirements without section to the document
+ no_section_requirements = get_requirements_without_section(all_specification_requirements)
+ if len(no_section_requirements) >0:
+ counter = 0
+ for requirement in no_section_requirements:
+ counter += 1
+ reqdata = no_section_requirements[requirement]
+
+ req_vms = get_requirement_verification_methods(reqdata['verification_methods'])
+ #req_vms_text = get_requirement_verification_methods_text(reqdata['verification_methods'])
+ req_vms_text = get_requirement_verification_methods_and_text(requirement,all_project_requirement_vms,all_project_requirement_ver_methods)
+
+ req_applicability = get_requirement_applicability(requirement,all_project_component_types, all_project_applicability_conditions)
+ req_type = get_requirement_type(reqdata,all_project_requirement_types)
+
+ requirement_with_images = has_images(requirement, all_project_images)
+
+ template_data.append({
+ "specification_name" : CURRENT_SPECIFICATION["name"] if counter == 1 else "",
+ "section_name" : "",
+ "req_id" : reqdata['identifier'],
+ "req_title" : reqdata['title'],
+ "req_text" : reqdata['identifier']+"_docx",
+ "req_type" : req_type,
+ "req_rationale" : reqdata['comment'],
+ "req_ver_methods" : req_vms,
+ "req_ver_methods_text" : req_vms_text,
+ "req_ver_methods_and_text" : reqdata['identifier']+"vms_docx",
+ "req_applicability" : req_applicability,
+ "images" : "Images_Placeholder_"+str(requirement) if requirement_with_images == True else "No_Images"
+ })
+
+ docx_list[reqdata['identifier']+"_docx"] = new_parser.parse_html_string(reqdata['text'])
+ docx_list[reqdata['identifier']+"vms_docx"] = new_parser.parse_html_string(req_vms_text)
+ no_loose_requirements = 0
+ else : no_loose_requirements = 1
+
+
+ #we need to get the specification section in the correct order to be added to the document
+ specification_sections = get_specification_sections(api, DEFAULT_VALUES["project"])
+
+ #for each section we will get its ordered requirements and add to the document
+ for section in specification_sections:
+
+ section_requirements = get_section_requirements(all_specification_requirements,section)
+ section_name = specification_sections[section]['name']
+ counter = 0
+
+ for requirement in section_requirements:
+ counter += 1
+
+ reqdata = section_requirements[requirement]
+
+ req_vms = get_requirement_verification_methods(reqdata['verification_methods'])
+ #req_vms_text = get_requirement_verification_methods_text(reqdata['verification_methods'])
+ req_vms_text = get_requirement_verification_methods_and_text(requirement,all_project_requirement_vms,all_project_requirement_ver_methods)
+
+ requirement_with_images = has_images(requirement, all_project_images)
+
+ req_applicability = get_requirement_applicability(requirement,all_project_component_types, all_project_applicability_conditions)
+
+ req_type = get_requirement_type(reqdata,all_project_requirement_types)
+
+ template_data.append({
+ "specification_name" : CURRENT_SPECIFICATION["name"] if counter == 1 and no_loose_requirements == 1 else "",
+ "section_name" : section_name if counter == 1 else "",
+ "req_id" : reqdata['identifier'],
+ "req_title" : reqdata['title'],
+ "req_text" : reqdata['identifier']+"_docx",
+ "req_type" : req_type,
+ "req_rationale" : reqdata['comment'],
+ "req_ver_methods" : req_vms,
+ "req_ver_methods_text" : req_vms_text,
+ "req_ver_methods_and_text" : reqdata['identifier']+"vms_docx",
+ "req_applicability" : req_applicability,
+ "images" : "Images_Placeholder_"+str(requirement) if requirement_with_images == True else "No_Images"
+ })
+ docx_list[reqdata['identifier']+"_docx"] = new_parser.parse_html_string(reqdata['text'])
+ docx_list[reqdata['identifier']+"vms_docx"] = new_parser.parse_html_string(req_vms_text)
+ no_loose_requirements = 0
+
+ print ("Going to merge data into templates")
+
+ document.merge_templates(template_data, separator='continuous_section')
+ document.write(OUTPUT_FILE)
+
+ document2 = Document(OUTPUT_FILE)
+ remove_all_but_last_section(document2)
+ remove_all_empty_headings(document2)
+ print ("Going to put html formatting into document")
+ put_html_text(document2, docx_list)
+ print ("Going to put images into document")
+ put_images(document2, all_project_images)
+ keep_tables_on_one_page(document2)
+
+ document2.save(OUTPUT_FILE)
+ create_file(api, OUTPUT_FILE, open(OUTPUT_FILE,'rb'), DEFAULT_VALUES["project"])
+ print ("Specification document created -> "+ specification_data['name'])
+ return
+
+def main(**kwargs):
+ #VALISPACE['domain'] = input("Enter your Valispace's deployment [https://....]: ")
+ #VALISPACE['username'] = input("Enter your username for Valispace's deployment: ")
+ #VALISPACE['password'] = getpass.getpass("Enter your password for Valispace's deployment: ")
+ #DEFAULT_VALUES['project'] = input("Enter the project_ID to generate Specifications: ")
+ #DEFAULT_VALUES['files_directory'] = input("Enter your Template File Path (example c:/templates/): ")
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password'),
+ warn_https = VALISPACE.get('warn_https', False),
+ )
+
+
+ all_project_specifications = get_map(api, f"requirements/specifications/?project="+str(DEFAULT_VALUES["project"]), "name")
+ all_project_images = get_map(api, f"files/?project="+str(DEFAULT_VALUES["project"]), "id", None, filter_images)
+ all_project_component_types = get_map(api, f"components/types/", "id", )
+ all_project_applicability_conditions = get_map(api, f"requirements/applicability-conditions/?project="+str(DEFAULT_VALUES["project"]), "id")
+ all_project_requirement_types = get_map(api, f"requirements/types/?project="+str(DEFAULT_VALUES["project"]), "id")
+ all_project_requirement_ver_methods = get_map(api, f"requirements/verification-methods/?project="+str(DEFAULT_VALUES["project"]), "id")
+ all_project_requirement_vms = get_map(api, f"requirements/requirement-vms/?project="+str(DEFAULT_VALUES["project"])+"&clean_html=text", "id")
+ all_project_requirements = get_map(api, f"requirements/complete/?project="+str(DEFAULT_VALUES["project"])+"&clean_html=text&clean_text=comment", "id")
+
+
+ #all_deployment_user_groups = get_map(api, f"group/", "id")
+ #all_deployment_users = get_map(api, f"user/", "id")
+ #all_project_files = get_map(api, f"files/?project="+str(DEFAULT_VALUES["project"]), "id", None, filter_not_images)
+ #all_project_custom_fields = get_map(api, f"data/custom-fields/?project="+str(DEFAULT_VALUES["project"]), "id")
+ #all_project_custom_field_options = get_map(api, f"data/custom-field-options/?project="+str(DEFAULT_VALUES["project"]), "id")
+ import_file = api.get("files/189/")
+ readfile = BytesIO(requests.get(import_file['download_url']).content)
+
+ for specification in all_project_specifications:
+ create_specification_document(api, MailMerge(readfile, remove_empty_tables=False, auto_update_fields_on_open="auto"),all_project_requirements, all_project_specifications[specification], all_project_images, all_project_component_types,all_project_applicability_conditions,all_project_requirement_types,all_project_requirement_vms,all_project_requirement_ver_methods)
+
+ pass
+
+if __name__=='__main__':
+ main()
diff --git a/templates/testdashboard.py b/templates/testdashboard.py
new file mode 100644
index 0000000..c9bf105
--- /dev/null
+++ b/templates/testdashboard.py
@@ -0,0 +1,68 @@
+import csv, json
+from valispace import API
+
+import warnings
+import os
+import urllib.request
+
+from ast import Str
+from collections.abc import Callable
+from xmlrpc.client import Boolean
+
+VALISPACE = {
+ 'domain': 'https://.valispace.com/',
+ 'username': '',
+ 'password': ''
+}
+
+DEFAULT_VALUES = {
+ "project": 24,
+}
+
+def get_map(api: API, endpoint: Str = "/", name: Str = "id", name_map_func: Callable[[Str], Boolean] = None, filter_func: Callable[[Str], Boolean]= None):
+ """
+ Function that given an endpoint returns a dict with specific keys.
+ If function is provided it generates the key. name_map_func must receive an object instance.
+ Otherwise key will be the property of each object specified in name.
+ """
+ mapping = {}
+ if not name_map_func:
+ name_map_func = lambda x: x[name]
+ for inst in api.get(endpoint):
+ if filter_func and not filter_func(inst):
+ # Filtered out
+ continue
+
+ key = name_map_func(inst)
+ if not mapping.get(key):
+ mapping[key] = inst
+ else:
+ warnings.warn(f"Warning ---> Key: {key} already has an object. Some data may be lost in mapping.")
+ return mapping
+
+def main(**kwargs):
+
+ api = API(
+ url = VALISPACE.get('domain'),
+ username = VALISPACE.get('username'),
+ password = VALISPACE.get('password')
+ )
+
+ all_project_test_runs = get_map(api, f"testing/test-runs/?project="+str(DEFAULT_VALUES["project"]), "id")
+
+ pass_runs = 0
+ failed_runs = 0
+
+ for run in all_project_test_runs:
+ if all_project_test_runs[run]['status'] == 50:
+ pass_runs += 1
+ elif all_project_test_runs[run]['status'] == 40:
+ failed_runs += 1
+
+ api.request('patch', "dashboards/blocks/blocktext/12/", {"text": "Number of Passed Test Runs -> "+ str(pass_runs) + "new line
"})
+ api.request('patch', "dashboards/blocks/blocktext/13/", {"text": "Number of Failed Test Runs -> "+ str(failed_runs)})
+ #api.request('patch', "dashboards/blocks/blocktext/97/", {"text": "Number of Failed Test Runs -> "+ str(failed_runs)})
+ pass
+
+if __name__=='__main__':
+ main()