Compare commits
1 Commits
master
...
revert-RJ4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdeaa2a83e |
@@ -1,4 +1,4 @@
|
|||||||
from python:3.6
|
from python:3.5
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|
||||||
|
|||||||
@@ -44,10 +44,9 @@ AWS_S3_BUCKET="..."
|
|||||||
AWS_ACCESS_KEY_ID="..."
|
AWS_ACCESS_KEY_ID="..."
|
||||||
AWS_SECRET_ACCESS_KEY="..."
|
AWS_SECRET_ACCESS_KEY="..."
|
||||||
|
|
||||||
SFDC_BASE_URL="https://test.salesforce.com"
|
SF_BASE_URL="https://test.salesforce.com"
|
||||||
SFDC_ACCESS_KEY_ID="..."
|
SFDC_ACCESS_KEY_ID="..."
|
||||||
SFDC_SECRET_ACCESS_KEY="..."
|
SFDC_SECRET_ACCESS_KEY="..."
|
||||||
SFDC_API_URL="https://sunpower--qa.cs8.my.salesforce.com"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ def s3_upload(bytes_or_file_like, filename=None, file_extension=None):
|
|||||||
|
|
||||||
# Assuming bucket already exists
|
# Assuming bucket already exists
|
||||||
s3.Bucket(bucket_name).put_object(Key=filename, Body=bytes_or_file_like.read(), ACL='public-read')
|
s3.Bucket(bucket_name).put_object(Key=filename, Body=bytes_or_file_like.read(), ACL='public-read')
|
||||||
# file_url = 'https://s3.amazonaws.com/{}/{}'.format(bucket_name, filename) # PermanentRedirect error
|
file_url = 'https://s3.amazonaws.com/{}/{}'.format(bucket_name, filename)
|
||||||
file_url = 'https://{}.s3.amazonaws.com/{}'.format(bucket_name, filename)
|
|
||||||
print('Uploaded filename {} to S3: {}'.format(filename, file_url))
|
print('Uploaded filename {} to S3: {}'.format(filename, file_url))
|
||||||
return file_url
|
return file_url
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ from helix.calculators.subarray_helper import extract_subarray
|
|||||||
|
|
||||||
from helix.constants.global_constants import minimum_racking_capacity
|
from helix.constants.global_constants import minimum_racking_capacity
|
||||||
from helix.constants.panel_type import PanelType
|
from helix.constants.panel_type import PanelType
|
||||||
from helix.constants.file_validation_error import FileValidationMessage,FileValidationException
|
|
||||||
from helix.models.subarray import Subarray
|
from helix.models.subarray import Subarray
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SeismicCalculator(object):
|
class SeismicCalculator(object):
|
||||||
def __init__(self, values, graph_repository):
|
def __init__(self, values, graph_repository):
|
||||||
self.values = values
|
self.values = values
|
||||||
@@ -39,20 +37,13 @@ class SeismicCalculator(object):
|
|||||||
more_anchors_needed = True
|
more_anchors_needed = True
|
||||||
perimeter_covered = sds < 1.0
|
perimeter_covered = sds < 1.0
|
||||||
anchor_threshold = 0
|
anchor_threshold = 0
|
||||||
was_rung_empty = False
|
|
||||||
while more_anchors_needed:
|
while more_anchors_needed:
|
||||||
rung = graph.pop_rung()
|
rung = graph.pop_rung()
|
||||||
interval = int(self.seismic_anchor_interval())
|
interval = int(self.seismic_anchor_interval())
|
||||||
nodes_since_last_anchor = interval
|
nodes_since_last_anchor = interval
|
||||||
if len(rung) == 0:
|
if len(rung) == 0:
|
||||||
if was_rung_empty:
|
|
||||||
# detected an infinite loop
|
|
||||||
# something is wrong with the input file
|
|
||||||
# probably panels overlapping
|
|
||||||
raise FileValidationException(FileValidationMessage.PanelsTooClose.value)
|
|
||||||
graph.reset()
|
graph.reset()
|
||||||
anchor_threshold += 1
|
anchor_threshold += 1
|
||||||
was_rung_empty = True
|
|
||||||
continue
|
continue
|
||||||
while more_anchors_needed and interval >= 0:
|
while more_anchors_needed and interval >= 0:
|
||||||
for node in rung:
|
for node in rung:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from helix.constants.global_constants import system_force_capacity
|
|||||||
|
|
||||||
|
|
||||||
class AnchorType(Enum):
|
class AnchorType(Enum):
|
||||||
# These values are being used by Salesforce integration, do not change it
|
|
||||||
OMG_PowerGrip = 'OMG PowerGrip'
|
OMG_PowerGrip = 'OMG PowerGrip'
|
||||||
OMG_PowerGrip_Plus = 'OMG PowerGrip Plus'
|
OMG_PowerGrip_Plus = 'OMG PowerGrip Plus'
|
||||||
EcoFasten = 'EcoFasten Eco 65'
|
EcoFasten = 'EcoFasten Eco 65'
|
||||||
|
|||||||
@@ -49,7 +49,3 @@ class FileValidationError(object):
|
|||||||
if self.__class__ != other.__class__:
|
if self.__class__ != other.__class__:
|
||||||
return False
|
return False
|
||||||
return self.row_number == other.row_number and self.validation_message == other.validation_message
|
return self.row_number == other.row_number and self.validation_message == other.validation_message
|
||||||
|
|
||||||
class FileValidationException(Exception):
|
|
||||||
def __init__(self, message):
|
|
||||||
self.message = message
|
|
||||||
|
|||||||
@@ -7,5 +7,3 @@ parapet_coefficients = 0.88, 1.2
|
|||||||
system_force_capacity = 418.
|
system_force_capacity = 418.
|
||||||
|
|
||||||
minimum_racking_capacity = 226
|
minimum_racking_capacity = 226
|
||||||
|
|
||||||
max_corner_angle = 135
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class ModuleType(Enum):
|
class ModuleType(Enum):
|
||||||
# These values are being used by Salesforce integration, do not change it
|
|
||||||
Cell96 = '96 Cell'
|
Cell96 = '96 Cell'
|
||||||
Cell128 = '128 Cell'
|
Cell128 = '128 Cell'
|
||||||
PSeries = 'P-Series'
|
PSeries = 'P-Series'
|
||||||
|
|||||||
@@ -5,18 +5,12 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
class JsonBuilder:
|
class JsonBuilder:
|
||||||
def build_bom(self, rows):
|
def build_bom_output(self, rows):
|
||||||
data = []
|
data = []
|
||||||
headers = ['itemId', 'description', 'quantity']
|
headers = ['Part #', 'Description', 'Total']
|
||||||
for row in rows:
|
for row in rows:
|
||||||
d = {}
|
d = {}
|
||||||
for i, value in enumerate(row):
|
for i, value in enumerate(row):
|
||||||
d[headers[i]] = value
|
d[headers[i]] = value
|
||||||
data.append(d)
|
data.append(d)
|
||||||
return data
|
|
||||||
|
|
||||||
def bom_to_json(self, data):
|
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
def build_bom_output(self, rows):
|
|
||||||
return self.bom_to_json(self.build_bom(rows))
|
|
||||||
|
|||||||
354
helix/main.py
354
helix/main.py
@@ -1,10 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urlparse
|
|
||||||
import rollbar
|
import rollbar
|
||||||
import rollbar.contrib.flask
|
import rollbar.contrib.flask
|
||||||
from flask import Flask, request, make_response, session, render_template, \
|
from flask import Flask, request, make_response, session, render_template, \
|
||||||
redirect, url_for, jsonify, flash
|
redirect, url_for
|
||||||
from flask import got_request_exception
|
from flask import got_request_exception
|
||||||
from flask.ext import assets
|
from flask.ext import assets
|
||||||
from flask_oauthlib.client import OAuth
|
from flask_oauthlib.client import OAuth
|
||||||
@@ -17,7 +16,6 @@ from helix.Services.dxf_service import DXFService
|
|||||||
from helix.api.api import api
|
from helix.api.api import api
|
||||||
from helix.calculators.calculator import Calculator
|
from helix.calculators.calculator import Calculator
|
||||||
from helix.constants import redis_constant, sql_constant
|
from helix.constants import redis_constant, sql_constant
|
||||||
from helix.constants.file_validation_error import FileValidationError, FileValidationException
|
|
||||||
from helix.constants.inverter_type import InverterType
|
from helix.constants.inverter_type import InverterType
|
||||||
from helix.constants.system_type import SystemType
|
from helix.constants.system_type import SystemType
|
||||||
from helix.csv_builder import CsvBuilder
|
from helix.csv_builder import CsvBuilder
|
||||||
@@ -29,48 +27,28 @@ from helix.forms.input_form import InputForm, ArrayForm, TestDXFForm
|
|||||||
from helix.models.dxf.dxf_error import DXFError, OldDxfFormatException
|
from helix.models.dxf.dxf_error import DXFError, OldDxfFormatException
|
||||||
from helix.presenters.image_presenter import ImagePresenter
|
from helix.presenters.image_presenter import ImagePresenter
|
||||||
from helix.presenters.panel_presenter import ProjectPresenter
|
from helix.presenters.panel_presenter import ProjectPresenter
|
||||||
from helix.qa_helper import QAScenario
|
|
||||||
from helix.session_manager import SessionManager
|
from helix.session_manager import SessionManager
|
||||||
from helix.validators.file_validator import FileValidator, FileType
|
from helix.validators.file_validator import FileValidator, FileType
|
||||||
from helix.validators.subarray_validator import SubarrayValidator
|
from helix.validators.subarray_validator import SubarrayValidator
|
||||||
from flask_featureflags import FeatureFlag
|
|
||||||
import flask_featureflags as feature
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.register_blueprint(api, url_prefix='/api')
|
app.register_blueprint(api, url_prefix='/api')
|
||||||
app.secret_key = os.getenv('SECRET_KEY', 'verysecretkey')
|
app.secret_key = os.getenv('SECRET_KEY', 'verysecretkey')
|
||||||
app.config['PROFILE'] = True
|
app.config['PROFILE'] = True
|
||||||
|
|
||||||
app.config['FEATURE_FLAGS'] = {
|
|
||||||
'ff_dummy_feature': True,
|
|
||||||
'ff_cpp': True
|
|
||||||
}
|
|
||||||
|
|
||||||
FeatureFlag(app) # initializes feature flags
|
# Sales Force integrations
|
||||||
|
|
||||||
if feature.is_active('ff_dummy_feature'):
|
|
||||||
app.logger.info('Feature flags working!')
|
|
||||||
app.logger.info('Current state: ')
|
|
||||||
app.logger.info(pprint.PrettyPrinter(width=41, compact=True).pformat(app.config['FEATURE_FLAGS']))
|
|
||||||
|
|
||||||
# Salesforce integrations
|
|
||||||
oauth = OAuth()
|
oauth = OAuth()
|
||||||
SFDC_BASE_URL = os.getenv('SFDC_BASE_URL', 'https://test.salesforce.com')
|
SF_BASE_URL = os.getenv('SFDC_BASE_URL', 'https://test.salesforce.com')
|
||||||
try:
|
sales_force = oauth.remote_app('sales_force',
|
||||||
sales_force = oauth.remote_app('sales_force',
|
consumer_key=os.getenv('SFDC_ACCESS_KEY_ID'),
|
||||||
consumer_key=os.getenv('SFDC_ACCESS_KEY_ID'),
|
consumer_secret=os.getenv('SFDC_SECRET_ACCESS_KEY'),
|
||||||
consumer_secret=os.getenv('SFDC_SECRET_ACCESS_KEY'),
|
base_url=SF_BASE_URL,
|
||||||
base_url=SFDC_BASE_URL,
|
request_token_url=None, # OAuth 2
|
||||||
request_token_url=None, # OAuth 2
|
access_token_method='POST', # Sales Force requirement
|
||||||
access_token_method='POST', # Sales Force requirement
|
access_token_url=SF_BASE_URL + '/services/oauth2/token',
|
||||||
access_token_url=SFDC_BASE_URL + '/services/oauth2/token',
|
authorize_url=SF_BASE_URL + '/services/oauth2/authorize',
|
||||||
authorize_url=SFDC_BASE_URL + '/services/oauth2/authorize',
|
)
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
print('Salesforce integration disabled')
|
|
||||||
sales_force = None
|
|
||||||
|
|
||||||
|
|
||||||
assets_env = assets.Environment(app)
|
assets_env = assets.Environment(app)
|
||||||
assets_env.init_app(app)
|
assets_env.init_app(app)
|
||||||
@@ -105,8 +83,8 @@ def init_rollbar():
|
|||||||
got_request_exception.connect(rollbar.contrib.flask.report_exception, app)
|
got_request_exception.connect(rollbar.contrib.flask.report_exception, app)
|
||||||
|
|
||||||
|
|
||||||
def is_sales_force_session():
|
def is_sfdc_session():
|
||||||
return 'sf_session' in session
|
return 'SFID' in session
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@@ -151,7 +129,7 @@ def test_dxf():
|
|||||||
# wizard steps
|
# wizard steps
|
||||||
@app.route("/site_characterization/", methods=['GET', 'POST'])
|
@app.route("/site_characterization/", methods=['GET', 'POST'])
|
||||||
def site_characterization():
|
def site_characterization():
|
||||||
if is_sales_force_session():
|
if is_sfdc_session():
|
||||||
return redirect('/summary/')
|
return redirect('/summary/')
|
||||||
|
|
||||||
db_session = sql_constant.sql_session_maker()
|
db_session = sql_constant.sql_session_maker()
|
||||||
@@ -199,51 +177,13 @@ def summary():
|
|||||||
else:
|
else:
|
||||||
context['no_proceed'] = True
|
context['no_proceed'] = True
|
||||||
|
|
||||||
if is_sales_force_session():
|
if is_sfdc_session():
|
||||||
context['hide_back'] = True
|
context['hide_back'] = True
|
||||||
|
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return render_template('site_summary.html.jinja', context=context)
|
return render_template('site_summary.html.jinja', context=context)
|
||||||
|
|
||||||
|
|
||||||
def handle_dxf_file(session_manager, file_contents, filename=None):
|
|
||||||
errors = []
|
|
||||||
user_values = session_manager.user_values()
|
|
||||||
validator = FileValidator(user_values)
|
|
||||||
calculator = Calculator(user_values, calculate_panel_data=False)
|
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1] or 'dxf'
|
|
||||||
extension = extension.split('.')[-1]
|
|
||||||
|
|
||||||
validation_error = validator.validate(file_contents, FileType.AuroraDxf, extension=extension)
|
|
||||||
if validation_error:
|
|
||||||
error_msg = validation_error.format_error_message()
|
|
||||||
errors.append(error_msg)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
module_constants = user_values.module_system_constants()
|
|
||||||
# FIXME: parsing a file with many entities is very slow
|
|
||||||
dxf_data = DXFService().parse(file_contents,
|
|
||||||
module_constants,
|
|
||||||
user_values.system_type(),
|
|
||||||
calculator.L_B() * 12,
|
|
||||||
DXFHelper(),
|
|
||||||
SubarrayValidator())
|
|
||||||
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
|
|
||||||
session_manager.save_uploaded_file(csv, dxf_file_name=filename)
|
|
||||||
|
|
||||||
buildings = dxf_data['buildings']
|
|
||||||
session_manager.save_buildings_polygons(buildings)
|
|
||||||
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
|
|
||||||
|
|
||||||
return True, errors
|
|
||||||
except DXFError as error:
|
|
||||||
errors.append(error.message)
|
|
||||||
except OldDxfFormatException as error:
|
|
||||||
errors.append(error.message)
|
|
||||||
return False, errors
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/array_summary/", methods=['GET', 'POST'])
|
@app.route("/array_summary/", methods=['GET', 'POST'])
|
||||||
def array_summary():
|
def array_summary():
|
||||||
"""This endpoint allows you to upload a file.
|
"""This endpoint allows you to upload a file.
|
||||||
@@ -262,7 +202,8 @@ def array_summary():
|
|||||||
validator = FileValidator(session_manager.user_values())
|
validator = FileValidator(session_manager.user_values())
|
||||||
file = request.files['file_upload']
|
file = request.files['file_upload']
|
||||||
file_contents = validator.obtain_stream(file)
|
file_contents = validator.obtain_stream(file)
|
||||||
validation_error = validator.validate(file_contents, FileType.Csv, file)
|
validation_error = validator.validate(file_contents, file,
|
||||||
|
FileType.Csv)
|
||||||
if not validation_error:
|
if not validation_error:
|
||||||
session_manager.save_uploaded_file(file_contents,
|
session_manager.save_uploaded_file(file_contents,
|
||||||
cad_file_name=file.filename)
|
cad_file_name=file.filename)
|
||||||
@@ -274,15 +215,36 @@ def array_summary():
|
|||||||
elif 'dxf_upload' in request.files and request.files['dxf_upload'].filename:
|
elif 'dxf_upload' in request.files and request.files['dxf_upload'].filename:
|
||||||
file = request.files['dxf_upload']
|
file = request.files['dxf_upload']
|
||||||
user_values = session_manager.user_values()
|
user_values = session_manager.user_values()
|
||||||
|
calculator = Calculator(user_values, calculate_panel_data=False)
|
||||||
validator = FileValidator(user_values)
|
validator = FileValidator(user_values)
|
||||||
file_contents = validator.obtain_stream(file)
|
file_contents = validator.obtain_stream(file)
|
||||||
success, errors = handle_dxf_file(session_manager, file_contents, filename=file.filename)
|
validation_error = validator.validate(file_contents, file,
|
||||||
if success:
|
FileType.AuroraDxf)
|
||||||
if is_sales_force_session():
|
if validation_error:
|
||||||
session['sf_session']['new_dxf_file'] = True
|
error_msg = validation_error.format_error_message()
|
||||||
return redirect(url_for('array_summary'))
|
array_form.dxf_upload.errors.append(error_msg)
|
||||||
else:
|
else:
|
||||||
array_form.dxf_upload.errors.extend(errors)
|
try:
|
||||||
|
module_constants = user_values.module_system_constants()
|
||||||
|
# FIXME: parsing a file with many entities is very slow
|
||||||
|
dxf_data = DXFService().parse(file_contents,
|
||||||
|
module_constants,
|
||||||
|
user_values.system_type(),
|
||||||
|
calculator.L_B() * 12,
|
||||||
|
DXFHelper(),
|
||||||
|
SubarrayValidator())
|
||||||
|
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
|
||||||
|
session_manager.save_uploaded_file(csv, dxf_file_name=file.filename)
|
||||||
|
|
||||||
|
buildings = dxf_data['buildings']
|
||||||
|
session_manager.save_buildings_polygons(buildings)
|
||||||
|
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
|
||||||
|
|
||||||
|
return redirect(url_for('array_summary'))
|
||||||
|
except DXFError as error:
|
||||||
|
array_form.dxf_upload.errors.append(error.message)
|
||||||
|
except OldDxfFormatException as error:
|
||||||
|
array_form.dxf_upload.errors.append(error.message)
|
||||||
elif context['csv_available']:
|
elif context['csv_available']:
|
||||||
return redirect(url_for('power_station_configuration'))
|
return redirect(url_for('power_station_configuration'))
|
||||||
else:
|
else:
|
||||||
@@ -294,91 +256,39 @@ def array_summary():
|
|||||||
context['dxf_file_name'] = ''
|
context['dxf_file_name'] = ''
|
||||||
if context['site_data_available'] and context['csv_available']:
|
if context['site_data_available'] and context['csv_available']:
|
||||||
user_values = session_manager.user_values()
|
user_values = session_manager.user_values()
|
||||||
try:
|
calculator = Calculator(user_values)
|
||||||
calculator = Calculator(user_values)
|
system_type = user_values.system_type()
|
||||||
system_type = user_values.system_type()
|
module_type = user_values.module_type()
|
||||||
module_type = user_values.module_type()
|
project_presenter = ProjectPresenter(system_type, module_type)
|
||||||
project_presenter = ProjectPresenter(system_type, module_type)
|
|
||||||
|
|
||||||
context['wind_zones'] = system_type.system_constants().wind_zones
|
context['wind_zones'] = system_type.system_constants().wind_zones
|
||||||
context['summary_table'] = calculator.summary_table()
|
context['summary_table'] = calculator.summary_table()
|
||||||
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
|
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
|
||||||
context['seismic_anchors'] = calculator.subarray_summary()
|
context['seismic_anchors'] = calculator.subarray_summary()
|
||||||
context['summary_values'] = calculator.summary_values()
|
context['summary_values'] = calculator.summary_values()
|
||||||
|
|
||||||
panels = calculator.get_computed_csv_columns()
|
panels = calculator.get_computed_csv_columns()
|
||||||
max_y = project_presenter.get_max_y(calculator.buildings_for_drawing,panels)
|
context['panel_array'] = project_presenter.get_panel_data(panels,
|
||||||
context['panel_array'] = project_presenter.get_panel_data(panels,
|
calculator.subarrays,
|
||||||
calculator.subarrays,
|
project_presenter.get_max_y(
|
||||||
max_y)
|
calculator.buildings_for_drawing,
|
||||||
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing, max_y)
|
panels))
|
||||||
context['corners'] = project_presenter.get_corners(calculator.buildings)
|
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing)
|
||||||
context['override_form'] = True
|
context['override_form'] = True
|
||||||
context['cad_file_name'] = session_manager.site.cad_file_name or 'Upload System Text Data'
|
context['cad_file_name'] = session_manager.site.cad_file_name or 'Upload System Text Data'
|
||||||
context['dxf_file_name'] = session_manager.site.dxf_file_name or 'Upload System DXF'
|
context['dxf_file_name'] = session_manager.site.dxf_file_name or 'Upload System DXF'
|
||||||
context['is_drawing_inaccurate'] = session_manager.user_values().is_panel_drawing_inaccurate()
|
context['is_drawing_inaccurate'] = session_manager.user_values().is_panel_drawing_inaccurate()
|
||||||
context['inaccurate_drawing_warning'] = 'The subarrays in this design are not parallel to each other, \
|
context['inaccurate_drawing_warning'] = 'The subarrays in this design are not parallel to each other, \
|
||||||
and the graphical representation on this page may not be accurate.'
|
and the graphical representation on this page may not be accurate.'
|
||||||
|
|
||||||
except FileValidationException as error:
|
|
||||||
# when calculator is about to enter infinte loop
|
|
||||||
# it throws an exception - it is supplied wrong data
|
|
||||||
context['site_data_available'] = False
|
|
||||||
context['csv_available'] = False
|
|
||||||
context['no_proceed'] = True
|
|
||||||
context['cad_file_name'] = ''
|
|
||||||
context['dxf_file_name'] = ''
|
|
||||||
context['infinite_loop_detection_message'] = error.message
|
|
||||||
|
|
||||||
elif not context['site_data_available']:
|
elif not context['site_data_available']:
|
||||||
context['no_proceed'] = True
|
context['no_proceed'] = True
|
||||||
|
|
||||||
if is_sales_force_session() and \
|
|
||||||
session['sf_session'].get('dxf_link', None) and \
|
|
||||||
not session['sf_session'].get('dxf_link_loaded', None):
|
|
||||||
|
|
||||||
context['javascripts'].append('auto_dxf_load')
|
|
||||||
|
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return render_template('array_summary.html.jinja', context=context, form=array_form)
|
return render_template('array_summary.html.jinja', context=context, form=array_form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load_dxf/", methods=['GET', 'POST'])
|
|
||||||
def load_dxf_file():
|
|
||||||
if (not is_sales_force_session()) or session['sf_session'].get('dxf_link', None) is None:
|
|
||||||
errors = ['DXF link not found']
|
|
||||||
response = jsonify({'errors': errors})
|
|
||||||
response.status_code = 404
|
|
||||||
return response
|
|
||||||
|
|
||||||
dxf_link = session['sf_session'].get('dxf_link', None)
|
|
||||||
if not dxf_link:
|
|
||||||
session['sf_session']['dxf_link_loaded'] = True
|
|
||||||
response = jsonify({'status': 'success', 'messages': ['No DXF file to load from Salesforce.']})
|
|
||||||
response.status_code = 202
|
|
||||||
return response
|
|
||||||
|
|
||||||
print('Loading DXF file from {}'.format(dxf_link))
|
|
||||||
response = requests.get(dxf_link, timeout=30)
|
|
||||||
if response.status_code == 200:
|
|
||||||
file_contents = response.content.decode('utf-8')
|
|
||||||
filename = urlparse(dxf_link).path.strip('/')
|
|
||||||
|
|
||||||
db_session = sql_constant.sql_session_maker()
|
|
||||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
||||||
|
|
||||||
success, errors = handle_dxf_file(session_manager, file_contents, filename=filename)
|
|
||||||
session['sf_session']['dxf_link_loaded'] = True
|
|
||||||
if success:
|
|
||||||
return jsonify({'status': 'success', 'messages': ['DXF from Salesforce loaded successfully.']})
|
|
||||||
else:
|
|
||||||
errors = ['Unable to download DXF file from Salesforce ({})'.format(response.status_code)]
|
|
||||||
|
|
||||||
response = jsonify({'errors': errors})
|
|
||||||
response.status_code = 400
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/power_station_configuration/", methods=['GET', 'POST'])
|
@app.route("/power_station_configuration/", methods=['GET', 'POST'])
|
||||||
def power_station_configuration():
|
def power_station_configuration():
|
||||||
db_session = sql_constant.sql_session_maker()
|
db_session = sql_constant.sql_session_maker()
|
||||||
@@ -457,11 +367,6 @@ def download():
|
|||||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||||
context = session_manager.context()
|
context = session_manager.context()
|
||||||
context['current_step'] = 5
|
context['current_step'] = 5
|
||||||
if is_sales_force_session():
|
|
||||||
error, data = session['sf_session'].pop('export_urls', (None, None))
|
|
||||||
if data is not None:
|
|
||||||
context['sfdc_export_error'] = error
|
|
||||||
context['sfdc_export_urls'] = data
|
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return render_template('download.html.jinja', context=context)
|
return render_template('download.html.jinja', context=context)
|
||||||
|
|
||||||
@@ -551,139 +456,68 @@ def helix_documentation():
|
|||||||
return render_template('helix_documentation.jinja', context=context)
|
return render_template('helix_documentation.jinja', context=context)
|
||||||
|
|
||||||
|
|
||||||
# Salesforce Integration
|
# Sales Force Integration
|
||||||
@app.route('/sales_force_login')
|
@app.route('/sales_force_login')
|
||||||
def sales_force_login():
|
def sales_force_login():
|
||||||
# To test it locally: https://localhost:8443/sales_force_login?sfid=a3cL00000004QsQIAU
|
# To test it locally: https://localhost:8443/sales_force_login?SFID=a3cL00000004QsQIAU
|
||||||
sfid = request.args.get('sfid')
|
sfid = request.args.get('SFID')
|
||||||
if sfid:
|
if sfid:
|
||||||
session.clear()
|
session.clear()
|
||||||
session['sf_session'] = {}
|
session['SFID'] = sfid
|
||||||
session['sf_session']['sfid'] = sfid
|
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, SFID=sfid), SFID=sfid)
|
||||||
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, sfid=sfid), sfid=sfid)
|
|
||||||
else:
|
else:
|
||||||
flash('Missing sfid')
|
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sales_force_authorized')
|
@app.route('/sales_force_authorized')
|
||||||
def sales_force_authorized():
|
def sales_force_authorized():
|
||||||
if not is_sales_force_session():
|
|
||||||
flash('This is not a Salesforce session')
|
|
||||||
return redirect('/')
|
|
||||||
|
|
||||||
next_url = url_for('summary')
|
next_url = url_for('summary')
|
||||||
|
|
||||||
try:
|
resp = sales_force.authorized_response()
|
||||||
resp = sales_force.authorized_response()
|
|
||||||
except Exception as e:
|
|
||||||
flash('Error on authenticating to Salesforce.')
|
|
||||||
print('Error on authenticating to Salesforce.')
|
|
||||||
print(str(e))
|
|
||||||
return sales_force_logout()
|
|
||||||
|
|
||||||
if resp is None:
|
if resp is None:
|
||||||
flash('Unknown response from Salesforce.')
|
print('Unable to authenticate to SFDC.')
|
||||||
print('Unknown response from Salesforce.')
|
|
||||||
return redirect(next_url)
|
return redirect(next_url)
|
||||||
|
|
||||||
print('New Salesforce - OAuth2 login')
|
print('New Sales Force - OAuth2 login')
|
||||||
db_session = sql_constant.sql_session_maker()
|
db_session = sql_constant.sql_session_maker()
|
||||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||||
session['sf_session']['access_token'] = resp['access_token']
|
session['sales_force_token'] = resp['access_token']
|
||||||
|
|
||||||
helix_session_id = session['id']
|
data = sf_tasks.get_site_characterization_from_sales_force(session, resp['instance_url'])
|
||||||
access_token = session['sf_session']['access_token']
|
if data:
|
||||||
sfid = session['sf_session']['sfid']
|
session_manager.save_form_submission(data)
|
||||||
try:
|
return redirect(next_url)
|
||||||
data, status_code = sf_tasks.get_site_characterization_from_sales_force(helix_session_id, access_token, sfid)
|
else:
|
||||||
if data and status_code == 200:
|
|
||||||
session['sf_session']['dxf_link'] = data['dxf_link']
|
|
||||||
session_manager.save_form_submission(data)
|
|
||||||
flash('Connected to Salesforce.')
|
|
||||||
return redirect(next_url)
|
|
||||||
else:
|
|
||||||
flash('Unable to get site characterization from Salesforce. ({})'.format(status_code))
|
|
||||||
return sales_force_logout()
|
|
||||||
except Exception as e:
|
|
||||||
print(str(e))
|
|
||||||
flash('Unable to get site characterization from Salesforce.')
|
|
||||||
return sales_force_logout()
|
return sales_force_logout()
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME
|
||||||
|
from flask import jsonify
|
||||||
@app.route("/export-sfdc")
|
@app.route("/export-sfdc")
|
||||||
def export_sfdc():
|
def export_sfdc():
|
||||||
if not is_sales_force_session():
|
if not is_sfdc_session():
|
||||||
flash('This is not a Salesforce session')
|
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
db_session = sql_constant.sql_session_maker()
|
db_session = sql_constant.sql_session_maker()
|
||||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||||
helix_session_id = session_manager.session['id']
|
session_id = session_manager.session['id']
|
||||||
sf_session = session['sf_session']
|
data = sf_tasks.export_to_sfdc(session_id)
|
||||||
access_token = sf_session['access_token']
|
|
||||||
sfid = sf_session['sfid']
|
|
||||||
new_dxf_file = sf_session.get('new_dxf_file', False)
|
|
||||||
error, data = sf_tasks.export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=new_dxf_file, qa_test=session.get('qa_test', None))
|
|
||||||
data.pop('bom', None)
|
|
||||||
data['dxfUrlFromSF'] = sf_session['dxf_link']
|
|
||||||
sf_session['export_urls'] = (error, data)
|
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return redirect('/download')
|
return jsonify(data)
|
||||||
|
# return redirect('/download')
|
||||||
|
|
||||||
|
|
||||||
if sales_force:
|
@sales_force.tokengetter
|
||||||
@sales_force.tokengetter
|
def get_sales_force_token(token=None):
|
||||||
def get_sales_force_token(token=None):
|
return session.get('sales_force_token')
|
||||||
return session.get('sf_session', {}).get('access_token', None)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sales_force_logout')
|
@app.route('/sales_force_logout')
|
||||||
def sales_force_logout():
|
def sales_force_logout():
|
||||||
session.pop('sf_session', None)
|
session.pop('SFID', None)
|
||||||
|
session.pop('sales_force_token', None)
|
||||||
|
session.clear()
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
# End of Salesforce Integration
|
# End of Sales Force Integration
|
||||||
|
|
||||||
|
|
||||||
# QA scenario simulations
|
|
||||||
|
|
||||||
@app.route('/qa')
|
|
||||||
def qa_simulations():
|
|
||||||
test = request.args.get('test', None)
|
|
||||||
bad_request = None
|
|
||||||
if not test:
|
|
||||||
bad_request = 'Missing `test` parameter.'
|
|
||||||
elif test not in QAScenario.all():
|
|
||||||
bad_request = 'Unknown test scenario: <b>' + test + '</b>.'
|
|
||||||
if bad_request:
|
|
||||||
tests = ''.join(['<li><a href="/qa?test=' + s + '">' + s + '</a></li>' for s in QAScenario.all()])
|
|
||||||
tests = '<ul>' + tests + '</ul>'
|
|
||||||
return bad_request + tests, 400
|
|
||||||
|
|
||||||
if test.startswith('sf_') and 'sf_session' not in session:
|
|
||||||
return 'Please, connect to Salesforce first.', 400
|
|
||||||
|
|
||||||
url = None
|
|
||||||
if test == QAScenario.SF_RESET_ALL.value:
|
|
||||||
session['sf_session']['dxf_link_loaded'] = False
|
|
||||||
session['qa_test'] = None
|
|
||||||
url = request.url_root
|
|
||||||
elif test == QAScenario.SF_NO_DXF.value:
|
|
||||||
session['sf_session']['dxf_link'] = None
|
|
||||||
session['sf_session']['dxf_link_loaded'] = False
|
|
||||||
url = request.url_root + 'array_summary/'
|
|
||||||
elif test == QAScenario.SF_VALID_DXF.value:
|
|
||||||
session['sf_session']['dxf_link'] = 'https://sunpower-test-dgplatform-spectrum.s3.amazonaws.com/a5FL00000008gKcMAI-DXF.dxf'
|
|
||||||
session['sf_session']['dxf_link_loaded'] = False
|
|
||||||
url = request.url_root + 'array_summary/'
|
|
||||||
elif test in (QAScenario.SF_ERROR_UPLOAD_S3.value, QAScenario.SF_ERROR_NOTIFY_SF.value):
|
|
||||||
session['qa_test'] = test
|
|
||||||
url = request.url_root + 'export-sfdc'
|
|
||||||
|
|
||||||
msg = '<b>' + test + '</b> test enabled.'
|
|
||||||
if url:
|
|
||||||
msg += ' Access: <a href="' + url + '">' + url + '</a> to check.'
|
|
||||||
return msg, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('format_number')
|
@app.template_filter('format_number')
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
class Corner(object):
|
|
||||||
def __init__(self, x, y, length_ccw, length_cw, angle):
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.length_ccw = length_ccw
|
|
||||||
self.length_cw = length_cw
|
|
||||||
self.angle = angle
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dictionary(self):
|
|
||||||
return {"x": self.x, "y": self.y, "length_ccw": self.length_ccw, "length_cw": self.length_cw, "angle": self.angle}
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
import sys
|
import sys
|
||||||
from math import sqrt, atan, degrees
|
|
||||||
from helix.models.corner import Corner
|
|
||||||
from helix.constants.global_constants import max_corner_angle
|
|
||||||
import flask_featureflags as feature
|
|
||||||
|
|
||||||
class ProjectPresenter(object):
|
class ProjectPresenter(object):
|
||||||
def __init__(self, system_type, module_type):
|
def __init__(self, system_type, module_type):
|
||||||
@@ -22,6 +18,8 @@ class ProjectPresenter(object):
|
|||||||
spacing_x, spacing_y = module_constants.presenter_spacing
|
spacing_x, spacing_y = module_constants.presenter_spacing
|
||||||
wind_zones = system_constants.wind_zones
|
wind_zones = system_constants.wind_zones
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for panel in panels:
|
for panel in panels:
|
||||||
subarray = [x for x in subarrays if x.subarray_number == panel.subarray][0]
|
subarray = [x for x in subarrays if x.subarray_number == panel.subarray][0]
|
||||||
origin = subarray.origin
|
origin = subarray.origin
|
||||||
@@ -52,10 +50,9 @@ class ProjectPresenter(object):
|
|||||||
self.offset = height
|
self.offset = height
|
||||||
for panel in table_data:
|
for panel in table_data:
|
||||||
panel['y'] = height - panel['y'] + first_cell
|
panel['y'] = height - panel['y'] + first_cell
|
||||||
|
|
||||||
return table_data
|
return table_data
|
||||||
|
|
||||||
def get_buildings(self, buildings, max_y):
|
def get_buildings(self, buildings):
|
||||||
if self.offset is None:
|
if self.offset is None:
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
|
|
||||||
@@ -70,51 +67,11 @@ class ProjectPresenter(object):
|
|||||||
# origin = self.find_origin(building)
|
# origin = self.find_origin(building)
|
||||||
for point in building:
|
for point in building:
|
||||||
point.x = point.x * spacing_x
|
point.x = point.x * spacing_x
|
||||||
point.y = abs((point.y * spacing_y) - max_y)
|
point.y = point.y * spacing_y
|
||||||
presentable_building.append(point.__dict__)
|
presentable_building.append(point.__dict__)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_corners(self, buildings):
|
|
||||||
result = []
|
|
||||||
if feature.is_active('ff_cpp'):
|
|
||||||
for building in buildings:
|
|
||||||
presentable_building = []
|
|
||||||
result.append(presentable_building)
|
|
||||||
previous_corner = building[-1]
|
|
||||||
for i, corner in enumerate(building):
|
|
||||||
if (i+1 == len(building)):
|
|
||||||
next_corner = building[0]
|
|
||||||
else:
|
|
||||||
next_corner = building[i+1]
|
|
||||||
|
|
||||||
#x coordinate is stored as first element of corner variable
|
|
||||||
#y coordinate is stored as second element of corner variable
|
|
||||||
corner_length_ccw = sqrt((next_corner[0] - corner[0])**2 + (next_corner[1] - corner[1])**2)
|
|
||||||
corner_length_cw = sqrt((previous_corner[0] - corner[0])**2 + (previous_corner[1] - corner[1])**2)
|
|
||||||
|
|
||||||
k1 = float('Inf') if (corner[0]==previous_corner[0]) else (corner[1] - previous_corner[1]) / (corner[0] - previous_corner[0])
|
|
||||||
k2 = float('Inf') if (corner[0]==next_corner[0]) else (next_corner[1] - corner[1]) / (next_corner[0] - corner[0])
|
|
||||||
|
|
||||||
theta2 = degrees(atan(k2))
|
|
||||||
theta1 = degrees(atan(k1)) - theta2
|
|
||||||
|
|
||||||
if (theta1 < 0):
|
|
||||||
if (k1 < 0 and k2 < 0):
|
|
||||||
theta1 = 360 + theta1
|
|
||||||
elif (k1 <= 0 and k2 >= 0):
|
|
||||||
theta1 = 180 + theta1
|
|
||||||
else:
|
|
||||||
if (k1 > 0 and k2 > 0):
|
|
||||||
theta1 = 180 + theta1
|
|
||||||
|
|
||||||
if (theta1 < max_corner_angle):
|
|
||||||
presentable_building.append(Corner(corner[0], corner[1], corner_length_ccw,corner_length_cw, theta1).__dict__)
|
|
||||||
|
|
||||||
previous_corner = corner
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_max_y(self,buildings, panels):
|
def get_max_y(self,buildings, panels):
|
||||||
|
|
||||||
module_constants = self.system_type.module_constants(self.module_type)
|
module_constants = self.system_type.module_constants(self.module_type)
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class QAException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QAScenario(Enum):
|
|
||||||
SF_NO_DXF = 'sf_no_dxf'
|
|
||||||
SF_ERROR_UPLOAD_S3 = 'sf_error_upload_s3'
|
|
||||||
SF_ERROR_NOTIFY_SF = 'sf_error_notify_sf'
|
|
||||||
SF_VALID_DXF = 'sf_valid_dxf'
|
|
||||||
SF_RESET_ALL = 'sf_reset_all'
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s.%s>' % (self.__class__.__name__, self.name)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def all(cls):
|
|
||||||
return [s.value for s in list(cls)]
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import uuid
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -11,54 +11,51 @@ from helix.doc_gen_params_builder import DocGenParamsBuilder
|
|||||||
from helix.helpers.camel_case import convert_dict_keys_to_snake_case
|
from helix.helpers.camel_case import convert_dict_keys_to_snake_case
|
||||||
from helix.json_builder import JsonBuilder
|
from helix.json_builder import JsonBuilder
|
||||||
from helix.presenters.image_presenter import ImagePresenter
|
from helix.presenters.image_presenter import ImagePresenter
|
||||||
from helix.qa_helper import QAScenario, QAException
|
|
||||||
from helix.Services.doc_gen_service import DocGenService
|
from helix.Services.doc_gen_service import DocGenService
|
||||||
from helix.Services.s3_helper import s3_upload
|
from helix.Services.s3_helper import s3_upload
|
||||||
from helix.session_manager import SessionManager
|
from helix.session_manager import SessionManager
|
||||||
|
|
||||||
|
|
||||||
def get_site_characterization_from_sales_force(helix_session_id, access_token, sfid):
|
def get_site_characterization_from_sales_force(session, base_url):
|
||||||
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
|
'''
|
||||||
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
|
@base_url: Avoid URL_NOT_RESET errors
|
||||||
|
'''
|
||||||
|
access_token = session['sales_force_token']
|
||||||
|
sfid = session['SFID']
|
||||||
|
helix_id = session['id']
|
||||||
|
url = base_url + '/services/apexrest/v1/HelixRoofDetails'
|
||||||
headers = {'Authorization': 'Bearer {}'.format(access_token)}
|
headers = {'Authorization': 'Bearer {}'.format(access_token)}
|
||||||
params = {'sfid': sfid, 'helix_session_id': helix_session_id}
|
result = requests.get(url, headers=headers, params={'SFID': sfid, 'helix_session_id': helix_id})
|
||||||
result = requests.get(url, headers=headers, params=params, timeout=30)
|
|
||||||
if result.status_code == 200:
|
if result.status_code == 200:
|
||||||
data = result.json()
|
data = result.json()
|
||||||
if data:
|
if data:
|
||||||
data = convert_sales_force_data_format_to_helix(data)
|
data = convert_sales_force_data_format_to_helix(data)
|
||||||
return data, 200
|
return data
|
||||||
else:
|
else:
|
||||||
print('Error while getting data from Salesforce ({})'.format(result.status_code))
|
print('Error while getting data from Sales Force: {}'.format(result.status_code))
|
||||||
print(url, params)
|
|
||||||
print(result.content)
|
print(result.content)
|
||||||
# print(result.request.headers)
|
|
||||||
return None, result.status_code
|
|
||||||
|
|
||||||
|
|
||||||
def convert_sales_force_data_format_to_helix(data):
|
def convert_sales_force_data_format_to_helix(data):
|
||||||
data = convert_dict_keys_to_snake_case(data)
|
data = convert_dict_keys_to_snake_case(data)
|
||||||
|
|
||||||
if data['system_type'] in ('Single-Tilt', 'Single Tilt'):
|
if data['system_type'] == 'Single-Tilt':
|
||||||
data['system_type'] = SystemType.singleTilt.value
|
data['system_type'] = SystemType.singleTilt.value
|
||||||
elif data['system_type'] == ('Dual-Tilt', 'Dual Tilt'):
|
elif data['system_type'] == 'Dual-Tilt':
|
||||||
data['system_type'] = SystemType.dualTilt.value
|
data['system_type'] = SystemType.dualTilt.value
|
||||||
|
|
||||||
if data['anchor_type'] == 'OMG Power Grip Plus':
|
|
||||||
data['anchor_type'] = 'OMG PowerGrip Plus'
|
|
||||||
|
|
||||||
data['ballast_block_weight'] = data['ballast_weight']
|
data['ballast_block_weight'] = data['ballast_weight']
|
||||||
data['max_system_pressure'] = data['max_psf'] = data['system_pressure']
|
data['max_system_pressure'] = data['max_psf'] = data['system_pressure']
|
||||||
return data
|
return data
|
||||||
# data['spectral_response_acceleration']
|
# data['spectral_response_acceleration']
|
||||||
|
|
||||||
|
|
||||||
def export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=False, qa_test=None):
|
def export_to_sfdc(session_id):
|
||||||
step = 'Exporting to Salesforce'
|
step = 'Exporting to SFDC'
|
||||||
try:
|
try:
|
||||||
# 1. Load User Values
|
# 1. Load User Values
|
||||||
step = 'Loading User Values'
|
step = 'Loading User Values'
|
||||||
session = {'id': helix_session_id}
|
session = {'id': session_id}
|
||||||
db_session = sql_constant.sql_session_maker()
|
db_session = sql_constant.sql_session_maker()
|
||||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||||
user_values = session_manager.user_values()
|
user_values = session_manager.user_values()
|
||||||
@@ -70,9 +67,7 @@ def export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=False, qa_
|
|||||||
csv_file = CsvBuilder().build_bom_output(bom)
|
csv_file = CsvBuilder().build_bom_output(bom)
|
||||||
# 2.1 Generate BOM CSV file
|
# 2.1 Generate BOM CSV file
|
||||||
step = 'Generating BOM as JSON'
|
step = 'Generating BOM as JSON'
|
||||||
json_builder = JsonBuilder()
|
json_str = JsonBuilder().build_bom_output(bom)
|
||||||
bom_list = json_builder.build_bom(bom)
|
|
||||||
bom_list_json = json_builder.bom_to_json(bom_list)
|
|
||||||
|
|
||||||
# 3. Generate DOCUMENTATION PDF file
|
# 3. Generate DOCUMENTATION PDF file
|
||||||
step = 'Generating Documentation'
|
step = 'Generating Documentation'
|
||||||
@@ -87,52 +82,37 @@ def export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=False, qa_
|
|||||||
|
|
||||||
# 5. Save CSV/PDF/DXF files into AWS-S3
|
# 5. Save CSV/PDF/DXF files into AWS-S3
|
||||||
step = 'Uploading to S3'
|
step = 'Uploading to S3'
|
||||||
if qa_test == QAScenario.SF_ERROR_UPLOAD_S3.value:
|
filename = uuid.uuid4().hex
|
||||||
raise QAException(qa_test)
|
bom_csv_url = s3_upload(io.StringIO(csv_file), filename=filename + '.csv')
|
||||||
|
bom_json_url = s3_upload(io.StringIO(json_str), filename=filename + '.json')
|
||||||
bom_csv_url = s3_upload(io.StringIO(csv_file), filename=sfid + '-csv.csv')
|
doc_url = s3_upload(io.BytesIO(document), filename=filename + '.pdf')
|
||||||
doc_url = s3_upload(io.BytesIO(document), filename=sfid + '-pdf.pdf')
|
if dxf_contents: # Optional
|
||||||
bom_json_url = s3_upload(io.StringIO(bom_list_json), filename=sfid + '-json.json')
|
dxf_url = s3_upload(io.StringIO(dxf_contents), filename=filename + '.dxf')
|
||||||
# Send only if the file DXF file was uploaded in Helix
|
|
||||||
if new_dxf_file and dxf_contents: # Optional
|
|
||||||
dxf_url = s3_upload(io.StringIO(dxf_contents), filename=sfid + '-dxf.dxf')
|
|
||||||
else:
|
else:
|
||||||
dxf_url = None
|
dxf_url = None
|
||||||
|
|
||||||
# 6. Notify SFDC system with an API request
|
# 6. Notify SFDC system with an API request
|
||||||
step = 'Notifying Salesforce'
|
step = 'Notifying SFDC'
|
||||||
if qa_test == QAScenario.SF_ERROR_NOTIFY_SF.value:
|
SFDC_API_URL = 'https://localhost:8443/' # FIXME
|
||||||
raise QAException(qa_test)
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'dxfUrl': dxf_url,
|
'dxf_url': dxf_url,
|
||||||
'bomCsvUrl': bom_csv_url,
|
'bom_csv_url': bom_csv_url,
|
||||||
'documentationUrl': doc_url,
|
'bom_json_url': bom_json_url,
|
||||||
'bom': bom_list,
|
'documentation_url': doc_url,
|
||||||
'helixSessionId': helix_session_id,
|
|
||||||
'sfid': sfid,
|
|
||||||
}
|
}
|
||||||
headers = {'Authorization': 'Bearer {}'.format(access_token)}
|
print(data)
|
||||||
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
|
# result = requests.post(SFDC_API_URL, data=data, timeout=30)
|
||||||
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
|
|
||||||
result = requests.post(url, headers=headers, json=data, timeout=30)
|
|
||||||
# 7. Internal logs
|
# 7. Internal logs
|
||||||
if result.status_code <= 299:
|
# if result.status_code != 200: # FIXME
|
||||||
print('Salesforce notified successfully for session {}.'.format(helix_session_id))
|
# print('')
|
||||||
# print(result.content)
|
# else:
|
||||||
error = None
|
# print('')
|
||||||
else:
|
|
||||||
error = 'Helix Calculator was not able to notify Salesforce ({})'.format(result.status_code)
|
|
||||||
|
|
||||||
print(error)
|
|
||||||
print(result.content)
|
|
||||||
|
|
||||||
# Only Helix need this information for audit
|
|
||||||
data['bomJsonUrl'] = bom_json_url
|
|
||||||
|
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return error, data
|
return data
|
||||||
|
# return result.status_code
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error while {} for session {}'.format(step, helix_session_id))
|
msg = 'Error while {} for session {}'.format(step, session_id)
|
||||||
error = 'Error while {}'.format(step)
|
print(msg)
|
||||||
return error, {}
|
raise e
|
||||||
|
|||||||
@@ -72,12 +72,6 @@ i.icon-info-circled {
|
|||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info_message {
|
|
||||||
color: black;
|
|
||||||
padding-left: 5px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error_message {
|
.error_message {
|
||||||
color: red;
|
color: red;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
@@ -157,7 +151,7 @@ a.back {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: 2px 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|||||||
@@ -59,35 +59,3 @@ h1 {
|
|||||||
.spacer {
|
.spacer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner-panel {
|
|
||||||
position: fixed;
|
|
||||||
margin: 0 auto;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 0; /* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.msg-container {
|
|
||||||
background-color: $off-white;
|
|
||||||
border: 1px solid $medium-border-color;
|
|
||||||
margin: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
.msg-container li {
|
|
||||||
padding-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -81,17 +81,17 @@ class SessionManager(object):
|
|||||||
building_height=form_data.get('building_height', 0),
|
building_height=form_data.get('building_height', 0),
|
||||||
building_width=form_data.get('building_width', 0),
|
building_width=form_data.get('building_width', 0),
|
||||||
building_length=form_data.get('building_length', 0),
|
building_length=form_data.get('building_length', 0),
|
||||||
parapet_height=form_data.get('parapet_height', form_data.get('building_parapet_height', 0)),
|
parapet_height=form_data.get('building_parapet_height', 0),
|
||||||
wind_speed=form_data.get('wind_speed', 0),
|
wind_speed=form_data.get('wind_speed', 0),
|
||||||
exposure_category=form_data.get('exposure_category', 'C'),
|
exposure_category=form_data.get('exposure_category', 'C'),
|
||||||
exposure_transition_distance=form_data.get('exposure_category_transition_distance', 0),
|
exposure_transition_distance=form_data.get('exposure_category_transition_distance', 0),
|
||||||
ballast_block_weight=form_data.get('ballast_block_weight', form_data.get('ballast_weight', 0)),
|
ballast_block_weight=form_data.get('ballast_block_weight', 0),
|
||||||
max_psf=form_data.get('system_pressure', form_data.get('max_system_pressure', form_data.get('max_psf', 0))),
|
max_psf=form_data.get('max_system_pressure', 0),
|
||||||
system_type=form_data.get('system_type', SystemType.dualTilt.value),
|
system_type=form_data.get('system_type', SystemType.dualTilt.value),
|
||||||
module_type=form_data.get('module_type', ModuleType.Cell96.value),
|
module_type=form_data.get('module_type', ModuleType.Cell96.value),
|
||||||
anchor_type=form_data.get('anchor_type', AnchorType.OMG_PowerGrip_Plus.value),
|
anchor_type=form_data.get('anchor_type', AnchorType.OMG_PowerGrip_Plus.value),
|
||||||
spectral_response=form_data.get('design_spectral_response', form_data.get('spectral_response_acceleration', form_data.get('spectral_response', 1))),
|
spectral_response=form_data.get('design_spectral_response', 1),
|
||||||
seismic_importance_factor=form_data.get('seismic_importance_factor', form_data.get('importance_factor', 1))
|
seismic_importance_factor=form_data.get('importance_factor', 1)
|
||||||
)
|
)
|
||||||
self.db_session.add(self.site)
|
self.db_session.add(self.site)
|
||||||
self.db_session.commit()
|
self.db_session.commit()
|
||||||
|
|||||||
@@ -616,20 +616,13 @@ i.icon-info-circled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* line 75 */
|
/* line 75 */
|
||||||
.info_message {
|
|
||||||
color: black;
|
|
||||||
padding-left: 5px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 81 */
|
|
||||||
.error_message {
|
.error_message {
|
||||||
color: red;
|
color: red;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 87 */
|
/* line 81 */
|
||||||
input, select {
|
input, select {
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -640,7 +633,7 @@ input, select {
|
|||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 97 */
|
/* line 91 */
|
||||||
select {
|
select {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
@@ -648,14 +641,14 @@ select {
|
|||||||
background: #F4F4F4 url("https://cdn1.iconfinder.com/data/icons/cc_mono_icon_set/blacks/16x16/br_down.png") no-repeat calc(100% - 15px);
|
background: #F4F4F4 url("https://cdn1.iconfinder.com/data/icons/cc_mono_icon_set/blacks/16x16/br_down.png") no-repeat calc(100% - 15px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 104 */
|
/* line 98 */
|
||||||
.submit, .download {
|
.submit, .download {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-top: 1px solid #EAEAEA;
|
border-top: 1px solid #EAEAEA;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 110 */
|
/* line 104 */
|
||||||
.button, button {
|
.button, button {
|
||||||
background: #5199F5;
|
background: #5199F5;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -667,7 +660,7 @@ select {
|
|||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 121 */
|
/* line 115 */
|
||||||
.navigation_buttons {
|
.navigation_buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -675,18 +668,18 @@ select {
|
|||||||
border-bottom: 1px solid #EAEAEA;
|
border-bottom: 1px solid #EAEAEA;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
/* line 128 */
|
/* line 122 */
|
||||||
.navigation_buttons a {
|
.navigation_buttons a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
}
|
}
|
||||||
/* line 134 */
|
/* line 128 */
|
||||||
.navigation_buttons .button {
|
.navigation_buttons .button {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 139 */
|
/* line 133 */
|
||||||
.back {
|
.back {
|
||||||
border: 2px solid #5199F5;
|
border: 2px solid #5199F5;
|
||||||
color: #5199F5;
|
color: #5199F5;
|
||||||
@@ -694,12 +687,12 @@ select {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 146 */
|
/* line 140 */
|
||||||
a.back {
|
a.back {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 150 */
|
/* line 144 */
|
||||||
.sample_images {
|
.sample_images {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -707,18 +700,18 @@ a.back {
|
|||||||
border-bottom: 1px solid #EAEAEA;
|
border-bottom: 1px solid #EAEAEA;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
/* line 157 */
|
/* line 151 */
|
||||||
.sample_images a {
|
.sample_images a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: 2px 5px;
|
margin: 2px 5px;
|
||||||
}
|
}
|
||||||
/* line 163 */
|
/* line 157 */
|
||||||
.sample_images .button {
|
.sample_images .button {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 168 */
|
/* line 162 */
|
||||||
.image_button, button {
|
.image_button, button {
|
||||||
background: #5199F5;
|
background: #5199F5;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -839,41 +832,6 @@ h1 {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* line 63 */
|
|
||||||
.spinner-panel {
|
|
||||||
position: fixed;
|
|
||||||
margin: 0 auto;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 0;
|
|
||||||
/* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 84 */
|
|
||||||
.msg-container {
|
|
||||||
background-color: #FDFDFD;
|
|
||||||
border: 1px solid #D8D8D8;
|
|
||||||
margin: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 91 */
|
|
||||||
.msg-container li {
|
|
||||||
padding-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 3 */
|
/* line 3 */
|
||||||
.navigation_header {
|
.navigation_header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1065,3 +1023,21 @@ table .right_border_cell {
|
|||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner-panel {
|
||||||
|
position: fixed;
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0; /* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$("#sf_msg_container").empty();
|
|
||||||
$('#sf-spinner-panel').css('width', '100%'); // Workaround for Safari issue
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/load_dxf/'
|
|
||||||
}).done(function(data, textStatus, jqXHR) { // 200 or 202
|
|
||||||
if (data && data.messages) {
|
|
||||||
data.messages.forEach((msg) => {
|
|
||||||
$("#sf_msg_container").append('<li class="info_message">' + msg + '</li>');
|
|
||||||
})
|
|
||||||
$("#sf_msg_container").show();
|
|
||||||
}
|
|
||||||
if (jqXHR.status == 200) {
|
|
||||||
setTimeout(function() { window.location.reload() }, 1000);
|
|
||||||
}
|
|
||||||
}).fail(function(jqXHR, textStatus, errorThrown) { // 400, 404, 5xx
|
|
||||||
// console.log(jqXHR, textStatus, errorThrown)
|
|
||||||
if (jqXHR.responseJSON && jqXHR.responseJSON.errors) {
|
|
||||||
jqXHR.responseJSON.errors.forEach((msg) => {
|
|
||||||
$("#sf_msg_container").append('<li class="error_message">' + msg + '</li>');
|
|
||||||
})
|
|
||||||
$("#sf_msg_container").show();
|
|
||||||
}
|
|
||||||
}).always(function(r) {
|
|
||||||
$('#sf-spinner-panel').css('width', '0%'); // Workaround for Safari issue
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
inverterStringsPerInverter.val(inverterStringsPerInverterValue);
|
inverterStringsPerInverter.val(inverterStringsPerInverterValue);
|
||||||
inverterStringsPerInverterForm.css('display', 'block');
|
inverterStringsPerInverterForm.css('display', 'inherit');
|
||||||
} else {
|
} else {
|
||||||
inverterStringsPerInverter.append($('<option selected value="0"></option>'));
|
inverterStringsPerInverter.append($('<option selected value="0"></option>'));
|
||||||
inverterStringsPerInverterForm.css('display', 'none');
|
inverterStringsPerInverterForm.css('display', 'none');
|
||||||
@@ -160,8 +160,7 @@
|
|||||||
if (data) {
|
if (data) {
|
||||||
$('#standalone_inverter_id').val(uuid);
|
$('#standalone_inverter_id').val(uuid);
|
||||||
$('#standalone_ac_run_length').val(data['ac_run_length']);
|
$('#standalone_ac_run_length').val(data['ac_run_length']);
|
||||||
var attach_point = data['attachment_point'][1];
|
$('#attachment-point').val(data['attachment_point'][0]);
|
||||||
$('#attachment_point option[value=' + attach_point + ']').attr('selected','selected');
|
|
||||||
|
|
||||||
fillInverterData(data, '#inverter');
|
fillInverterData(data, '#inverter');
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
{% extends "layout.html.jinja" %}
|
{% extends "layout.html.jinja" %}
|
||||||
{% set title = "Helix Calculator" %}
|
{% set title = "Helix Calculator" %}
|
||||||
{% block contents %}
|
{% block contents %}
|
||||||
<script>
|
|
||||||
{%if context['corners'] and 'ff_cpp' is active_feature %}
|
|
||||||
var corners_data = {{ context['corners']|tojson}};
|
|
||||||
if (corners_data){
|
|
||||||
console.log("Corners data :");
|
|
||||||
console.log(corners_data);
|
|
||||||
}
|
|
||||||
{%endif%}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var is_csv_available = {{ context['csv_available']|tojson|safe }};
|
var is_csv_available = {{ context['csv_available']|tojson|safe }};
|
||||||
</script>
|
</script>
|
||||||
@@ -20,16 +10,6 @@
|
|||||||
<i class="icon-spin6 animate-spin"></i>
|
<i class="icon-spin6 animate-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if 'sf_session' in session %}
|
|
||||||
<ul id="sf_msg_container" class="msg-container" style="display:none;">
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div id="sf-spinner-panel" class="spinner-panel">
|
|
||||||
<p>Loading DXF file from Salesforce. Please wait, this may take a while.</p>
|
|
||||||
<i class="icon-spin6 animate-spin"></i>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not context['csv_available'] %}
|
{% if not context['csv_available'] %}
|
||||||
<form action="" method="post" enctype="multipart/form-data">
|
<form action="" method="post" enctype="multipart/form-data">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
@@ -53,11 +33,6 @@
|
|||||||
<span id="error_message_txt" class="error_message centered_error">{{ field.errors[0] }}</span>
|
<span id="error_message_txt" class="error_message centered_error">{{ field.errors[0] }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if context['infinite_loop_detection_message'] %}
|
|
||||||
<span id="error_message_txt_inf_loop" class="error_message centered_error">{{ context['infinite_loop_detection_message'] }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
{% extends "layout.html.jinja" %}
|
{% extends "layout.html.jinja" %}
|
||||||
{% set title = "Helix Calculator" %}
|
{% set title = "Helix Calculator" %}
|
||||||
{% block contents %}
|
{% block contents %}
|
||||||
{% if 'sfdc_export_urls' in context %}
|
{% for warning in context['warning_messages'] %}
|
||||||
{% if context['sfdc_export_error'] %}
|
<div class="summary_warning">{{ warning.value }}</div>
|
||||||
<div class="summary_warning" style="margin-top: 20px;">{{ context['sfdc_export_error'] }}</div>
|
{% endfor %}
|
||||||
{% else %}
|
|
||||||
<div class="msg-container">Documents exported to Salesforce successfully.</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<ul class="msg-container">
|
|
||||||
{% if context['sfdc_export_urls']['dxfUrl'] %}
|
|
||||||
<li><a href="{{ context['sfdc_export_urls']['dxfUrl'] }}">DXF file</a> (uploaded on Helix)</li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{{ context['sfdc_export_urls']['dxfUrlFromSF'] }}">DXF file</a> (uploaded on Salesforce)</li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a href="{{ context['sfdc_export_urls']['documentationUrl'] }}">Documentation file</a></li>
|
|
||||||
<li><a href="{{ context['sfdc_export_urls']['bomCsvUrl'] }}">BOM CSV file</a></li>
|
|
||||||
<li><a href="{{ context['sfdc_export_urls']['bomJsonUrl'] }}">BOM JSON file</a></li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="form_section">
|
<div class="form_section">
|
||||||
<h3>Download</h3>
|
<h3>Download</h3>
|
||||||
@@ -40,7 +25,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if 'sf_session' in session %}
|
{% if 'SFID' in session %}
|
||||||
<div class="download">
|
<div class="download">
|
||||||
<a href="/export-sfdc">
|
<a href="/export-sfdc">
|
||||||
<button>Export documents to Salesforce</button>
|
<button>Export documents to Salesforce</button>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
<div>Inverter Brand</div>
|
<div>Inverter Brand</div>
|
||||||
</div>
|
</div>
|
||||||
<form name="inverter_brand_form" action="" method="post" enctype="multipart/form-data">
|
<form name="inverter_brand_form" action="" method="post" enctype="multipart/form-data">
|
||||||
<div id="inverter_brand_form" class="form_section ebom_form">
|
<div id="inverter_brand_form"
|
||||||
|
class="form_section ebom_form">
|
||||||
{{ inverter_brand_form.csrf_token }}
|
{{ inverter_brand_form.csrf_token }}
|
||||||
{% for field in inverter_brand_form.group('hidden') %}
|
{% for field in inverter_brand_form.group('hidden') %}
|
||||||
{{ field }}
|
{{ field }}
|
||||||
|
|||||||
@@ -59,23 +59,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% with messages = get_flashed_messages() %}
|
|
||||||
{% if messages %}
|
|
||||||
<br>
|
|
||||||
<div class="summary_warning">
|
|
||||||
{% for message in messages %}
|
|
||||||
<div>{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% block contents %}{% endblock %}
|
{% block contents %}{% endblock %}
|
||||||
|
|
||||||
{% if 'sf_session' in session %}
|
|
||||||
<small><i>Salesforce project</i> (<a href="/sales_force_logout">Log out</a>)</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
|
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
|
||||||
|
|||||||
@@ -87,4 +87,7 @@
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "navigation_buttons.html.jinja" %}
|
{% include "navigation_buttons.html.jinja" %}
|
||||||
|
{% if 'SFID' in session %}
|
||||||
|
<small><i>Sales Force project</i> (<a href="/sales_force_logout">Logout</a>)</small>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -100,13 +100,13 @@ class FileValidator(object):
|
|||||||
finally:
|
finally:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def validate(self, stream, expected, file=None, extension=None):
|
def validate(self, stream, file, expected):
|
||||||
"""Validates the uploaded file by extension
|
"""Validates the uploaded file by extension
|
||||||
and content
|
and content
|
||||||
|
|
||||||
Arguments;
|
Arguments;
|
||||||
stream (string): File content
|
stream (string): File content
|
||||||
file (FileObject): A file object provided from the ui. Used only to get the file extension.
|
file (FileObject): A file object provided from the ui
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -114,8 +114,7 @@ class FileValidator(object):
|
|||||||
file_type = self.identify_file_type(stream)
|
file_type = self.identify_file_type(stream)
|
||||||
assert file_type == expected
|
assert file_type == expected
|
||||||
validator = file_type.validator(self.values)
|
validator = file_type.validator(self.values)
|
||||||
if extension is None:
|
extension = self.obtain_extension(file)
|
||||||
extension = self.obtain_extension(file)
|
|
||||||
file_type.valid_mapping(extension)
|
file_type.valid_mapping(extension)
|
||||||
return validator.validate(stream)
|
return validator.validate(stream)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ nose==1.3.7
|
|||||||
selenium==2.53.1
|
selenium==2.53.1
|
||||||
splinter==0.7.3
|
splinter==0.7.3
|
||||||
cssselect==0.9.1
|
cssselect==0.9.1
|
||||||
lxml==4.1.1
|
lxml==3.6.0
|
||||||
mockredispy==2.9.0.11
|
mockredispy==2.9.0.11
|
||||||
Flask-Testing==0.4.2
|
Flask-Testing==0.4.2
|
||||||
splinter[flask]
|
splinter[flask]
|
||||||
|
|||||||
@@ -23,5 +23,3 @@ blinker==1.4
|
|||||||
Flask-OAuthlib==0.9.4
|
Flask-OAuthlib==0.9.4
|
||||||
boto3==1.4.8
|
boto3==1.4.8
|
||||||
ujson==1.35
|
ujson==1.35
|
||||||
Flask-FeatureFlags==0.6
|
|
||||||
mock==2.0.0
|
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
python-3.5.1
|
python-3.5.1
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import mock
|
|
||||||
|
|
||||||
from numpy.testing import assert_array_equal, assert_equal
|
from numpy.testing import assert_array_equal, assert_equal
|
||||||
from helix.constants.module_type import ModuleType
|
from helix.constants.module_type import ModuleType
|
||||||
@@ -10,15 +9,11 @@ from helix.models.subarray import Subarray
|
|||||||
from helix.presenters.panel_presenter import ProjectPresenter
|
from helix.presenters.panel_presenter import ProjectPresenter
|
||||||
from helix.constants.system_type import SystemType
|
from helix.constants.system_type import SystemType
|
||||||
from helix.constants.panel_type import PanelType
|
from helix.constants.panel_type import PanelType
|
||||||
from test.test_helpers import feature_is_always_active
|
|
||||||
import flask_featureflags
|
|
||||||
|
|
||||||
|
|
||||||
flask_featureflags.is_active = feature_is_always_active
|
|
||||||
|
|
||||||
|
|
||||||
class PanelPresenterTest(unittest.TestCase):
|
class PanelPresenterTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_get_table_data_single_tilt_96cell(self):
|
def test_get_table_data_single_tilt_96cell(self):
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||||
panels = [
|
panels = [
|
||||||
@@ -71,70 +66,16 @@ class PanelPresenterTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_buildings_data(self):
|
def test_get_buildings_data(self):
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||||
buildings = [ [ Coordinate(0,0), Coordinate(60,0), Coordinate(60,60), Coordinate(0,60) ] ] # big square
|
buildings = [ [ Coordinate(-60,-60), Coordinate(60,-60), Coordinate(60,60), Coordinate(-60,60) ] ] # big square
|
||||||
expected_buildings = [[
|
expected_buildings = [[
|
||||||
{'x': 0, '_Coordinate__rounded_x': 0, 'y': 60, '_Coordinate__rounded_y': 0, 'rotation': 0.0},
|
{'x': -60, '_Coordinate__rounded_x': -60, 'y': -60, '_Coordinate__rounded_y': -60, 'rotation': 0.0},
|
||||||
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 60, '_Coordinate__rounded_y': 0, 'rotation': 0.0},
|
{'x': 60, '_Coordinate__rounded_x': 60, 'y': -60, '_Coordinate__rounded_y': -60, 'rotation': 0.0},
|
||||||
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 0, '_Coordinate__rounded_y': 60, 'rotation': 0.0},
|
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 60, '_Coordinate__rounded_y': 60, 'rotation': 0.0},
|
||||||
{'x': 0, '_Coordinate__rounded_x': 0, 'y': 0, '_Coordinate__rounded_y': 60, 'rotation': 0.0}
|
{'x': -60, '_Coordinate__rounded_x': -60, 'y': 60, '_Coordinate__rounded_y': 60, 'rotation': 0.0}
|
||||||
]]
|
]]
|
||||||
actual_buildings = self.subject.get_buildings(buildings,60)
|
actual_buildings = self.subject.get_buildings(buildings)
|
||||||
assert_array_equal(actual_buildings,expected_buildings)
|
assert_array_equal(actual_buildings,expected_buildings)
|
||||||
|
|
||||||
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
|
|
||||||
def test_get_corners_box_building(self):
|
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
|
||||||
buildings = [ [ [0, 0], [60, 0], [60,60], [0, 60] ] ] # big square
|
|
||||||
expected_corners = [[
|
|
||||||
{'x': 0, 'y': 0, 'length_cw': 60, 'length_ccw':60, 'angle':90},
|
|
||||||
{'x': 60, 'y': 0, 'length_cw': 60, 'length_ccw':60, 'angle':90},
|
|
||||||
{'x': 60, 'y': 60, 'length_cw': 60, 'length_ccw':60, 'angle':90},
|
|
||||||
{'x': 0, 'y': 60, 'length_cw': 60, 'length_ccw':60, 'angle':90}
|
|
||||||
]]
|
|
||||||
actual_corners = self.subject.get_corners(buildings)
|
|
||||||
assert_array_equal(actual_corners,expected_corners)
|
|
||||||
|
|
||||||
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
|
|
||||||
def test_get_corners_box_building_rotated_30_degrees(self):
|
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
|
||||||
buildings = [ [ [0, 0], [51.96, 30], [21.96, 81.96], [-30, 51.96] ] ] # big square
|
|
||||||
expected_corners = [[
|
|
||||||
{'x': 0, 'y': 0, 'length_cw': 59.99867998547968, 'length_ccw':59.99867998547968, 'angle':90},
|
|
||||||
{'x': 51.96, 'y': 30, 'length_cw': 59.99867998547968, 'length_ccw':59.998679985479676, 'angle':90},
|
|
||||||
{'x': 21.96, 'y': 81.96, 'length_cw': 59.998679985479676, 'length_ccw':59.998679985479676, 'angle':90},
|
|
||||||
{'x': -30, 'y': 51.96, 'length_cw': 59.998679985479676, 'length_ccw':59.99867998547968, 'angle':90}
|
|
||||||
]]
|
|
||||||
actual_corners = self.subject.get_corners(buildings)
|
|
||||||
assert_array_equal(actual_corners,expected_corners)
|
|
||||||
|
|
||||||
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
|
|
||||||
def test_get_corners_building(self):
|
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
|
||||||
buildings = [ [ [-3.42, 1.51], [-1.66, -1.64], [4.22, -0.87], [-0.8, 5.64]] ]
|
|
||||||
expected_corners = [[
|
|
||||||
{'x': -3.42, 'y': 1.51, 'length_cw': 4.890940604832571, 'length_ccw':3.6083375673570233, 'angle':118.41626123074676},
|
|
||||||
{'x': -1.66, 'y': -1.64, 'length_cw': 3.6083375673570233, 'length_ccw':5.930202357424239, 'angle':111.73284308914215},
|
|
||||||
{'x': 4.22, 'y': -0.87, 'length_cw': 5.930202357424239, 'length_ccw':8.22073597678456, 'angle':59.823982400990424},
|
|
||||||
{'x': -0.8, 'y': 5.64, 'length_cw': 8.22073597678456, 'length_ccw':4.890940604832571, 'angle':70.02691327912069}
|
|
||||||
]]
|
|
||||||
actual_corners = self.subject.get_corners(buildings)
|
|
||||||
assert_array_equal(actual_corners,expected_corners)
|
|
||||||
|
|
||||||
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
|
|
||||||
def test_get_corners_wild_building_with_big_angles(self):
|
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
|
||||||
buildings = [ [ [-3.58, 3.32], [-0.78, -2.9], [-1.56,0.88], [0.66, -2.16], [1.5, 1.16], [2.72, 2.36], [-0.8, 5.64] ] ]
|
|
||||||
expected_corners = [[
|
|
||||||
{'x': -3.58, 'y': 3.32, 'length_cw': 3.6208838699963852, 'length_ccw':6.821172919667115, 'angle':105.6106862572924},
|
|
||||||
{'x': -0.78, 'y': -2.9, 'length_cw': 6.821172919667115, 'length_ccw':3.8596372886580936, 'angle':12.576112527956795},
|
|
||||||
{'x': 0.66, 'y': -2.16, 'length_cw': 3.7643060449437424, 'length_ccw':3.424616766880639, 'angle':50.33783308603299},
|
|
||||||
{'x': 2.72, 'y': 2.36, 'length_cw': 1.7112568480505783, 'length_ccw':4.811319985201567, 'angle':87.50512700090906},
|
|
||||||
{'x': -0.8, 'y': 5.64, 'length_cw': 4.811319985201567, 'length_ccw':3.6208838699963852, 'angle':97.17527350158382}
|
|
||||||
]]
|
|
||||||
actual_corners = self.subject.get_corners(buildings)
|
|
||||||
print(actual_corners)
|
|
||||||
assert_array_equal(actual_corners,expected_corners)
|
|
||||||
|
|
||||||
def test_get_max_y(self):
|
def test_get_max_y(self):
|
||||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||||
panels = [
|
panels = [
|
||||||
|
|||||||
@@ -105,7 +105,3 @@ def assert_image_equal(image_1, image_2, error=5e-2):
|
|||||||
|
|
||||||
average_error = total_error / pixels
|
average_error = total_error / pixels
|
||||||
assert average_error <= error, "Images are not equal to within %f error (got %f)" % (error, average_error)
|
assert average_error <= error, "Images are not equal to within %f error (got %f)" % (error, average_error)
|
||||||
|
|
||||||
# used for mocking response of feature flags
|
|
||||||
def feature_is_always_active(feature_name):
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class FileValidatorTest(unittest.TestCase):
|
|||||||
with open('test/fixtures/input_single_tilt.csv', 'r',
|
with open('test/fixtures/input_single_tilt.csv', 'r',
|
||||||
newline='') as file:
|
newline='') as file:
|
||||||
cad_input = file.read()
|
cad_input = file.read()
|
||||||
eq_(self.subject.validate(cad_input, FileType.Csv, fake_file), None)
|
eq_(self.subject.validate(cad_input, fake_file, FileType.Csv), None)
|
||||||
|
|
||||||
def test_unknown_files_are_invalid(self):
|
def test_unknown_files_are_invalid(self):
|
||||||
fake_file = MagicMock()
|
fake_file = MagicMock()
|
||||||
type(fake_file).filename = PropertyMock(return_value="Hi")
|
type(fake_file).filename = PropertyMock(return_value="Hi")
|
||||||
result = self.subject.validate("Hi", FileType.Unknown, fake_file)
|
result = self.subject.validate("Hi", fake_file, FileType.Unknown)
|
||||||
self.should_have_error(result,
|
self.should_have_error(result,
|
||||||
FileValidationMessage.UnknownFileUploaded, None)
|
FileValidationMessage.UnknownFileUploaded, None)
|
||||||
|
|
||||||
@@ -62,7 +62,8 @@ class FileValidatorTest(unittest.TestCase):
|
|||||||
fake_file.read.return_value = open(fname, "rb").read()
|
fake_file.read.return_value = open(fname, "rb").read()
|
||||||
fake_file.filename.return_value = "expected_dual_tilt_pseries_image.png"
|
fake_file.filename.return_value = "expected_dual_tilt_pseries_image.png"
|
||||||
stream = self.subject.obtain_stream(fake_file)
|
stream = self.subject.obtain_stream(fake_file)
|
||||||
self.should_have_error(self.subject.validate(stream, FileType.AuroraDxf, fake_file),
|
self.should_have_error(self.subject.validate(stream, fake_file,
|
||||||
|
FileType.AuroraDxf),
|
||||||
FileValidationMessage.ExpectedDxfFile,
|
FileValidationMessage.ExpectedDxfFile,
|
||||||
None)
|
None)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user