Merge branch 'master' into remove-internal-corners
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from python:3.5
|
||||
from python:3.6
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from helix.constants.global_constants import system_force_capacity
|
||||
|
||||
|
||||
class AnchorType(Enum):
|
||||
# These values are being used by Salesforce integration, do not change it
|
||||
OMG_PowerGrip = 'OMG PowerGrip'
|
||||
OMG_PowerGrip_Plus = 'OMG PowerGrip Plus'
|
||||
EcoFasten = 'EcoFasten Eco 65'
|
||||
|
||||
@@ -8,4 +8,4 @@ system_force_capacity = 418.
|
||||
|
||||
minimum_racking_capacity = 226
|
||||
|
||||
max_corner_angle = 135
|
||||
max_corner_angle = 135
|
||||
|
||||
@@ -2,6 +2,7 @@ from enum import Enum
|
||||
|
||||
|
||||
class ModuleType(Enum):
|
||||
# These values are being used by Salesforce integration, do not change it
|
||||
Cell96 = '96 Cell'
|
||||
Cell128 = '128 Cell'
|
||||
PSeries = 'P-Series'
|
||||
|
||||
193
helix/main.py
193
helix/main.py
@@ -4,7 +4,7 @@ from urllib.parse import urlparse
|
||||
import rollbar
|
||||
import rollbar.contrib.flask
|
||||
from flask import Flask, request, make_response, session, render_template, \
|
||||
redirect, url_for, jsonify
|
||||
redirect, url_for, jsonify, flash
|
||||
from flask import got_request_exception
|
||||
from flask.ext import assets
|
||||
from flask_oauthlib.client import OAuth
|
||||
@@ -29,17 +29,32 @@ from helix.forms.input_form import InputForm, ArrayForm, TestDXFForm
|
||||
from helix.models.dxf.dxf_error import DXFError, OldDxfFormatException
|
||||
from helix.presenters.image_presenter import ImagePresenter
|
||||
from helix.presenters.panel_presenter import ProjectPresenter
|
||||
from helix.qa_helper import QAScenario
|
||||
from helix.session_manager import SessionManager
|
||||
from helix.validators.file_validator import FileValidator, FileType
|
||||
from helix.validators.subarray_validator import SubarrayValidator
|
||||
from flask_featureflags import FeatureFlag
|
||||
import flask_featureflags as feature
|
||||
import pprint
|
||||
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(api, url_prefix='/api')
|
||||
app.secret_key = os.getenv('SECRET_KEY', 'verysecretkey')
|
||||
app.config['PROFILE'] = True
|
||||
|
||||
app.config['FEATURE_FLAGS'] = {
|
||||
'ff_dummy_feature': True,
|
||||
'ff_cpp': True
|
||||
}
|
||||
|
||||
# Sales Force integrations
|
||||
FeatureFlag(app) # initializes feature flags
|
||||
|
||||
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()
|
||||
SFDC_BASE_URL = os.getenv('SFDC_BASE_URL', 'https://test.salesforce.com')
|
||||
try:
|
||||
@@ -53,7 +68,7 @@ try:
|
||||
authorize_url=SFDC_BASE_URL + '/services/oauth2/authorize',
|
||||
)
|
||||
except TypeError:
|
||||
print('Sales Force integration disabled')
|
||||
print('Salesforce integration disabled')
|
||||
sales_force = None
|
||||
|
||||
|
||||
@@ -90,8 +105,8 @@ def init_rollbar():
|
||||
got_request_exception.connect(rollbar.contrib.flask.report_exception, app)
|
||||
|
||||
|
||||
def is_sfdc_session():
|
||||
return 'SFID' in session
|
||||
def is_sales_force_session():
|
||||
return 'sf_session' in session
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@@ -136,7 +151,7 @@ def test_dxf():
|
||||
# wizard steps
|
||||
@app.route("/site_characterization/", methods=['GET', 'POST'])
|
||||
def site_characterization():
|
||||
if is_sfdc_session():
|
||||
if is_sales_force_session():
|
||||
return redirect('/summary/')
|
||||
|
||||
db_session = sql_constant.sql_session_maker()
|
||||
@@ -184,14 +199,14 @@ def summary():
|
||||
else:
|
||||
context['no_proceed'] = True
|
||||
|
||||
if is_sfdc_session():
|
||||
if is_sales_force_session():
|
||||
context['hide_back'] = True
|
||||
|
||||
db_session.close()
|
||||
return render_template('site_summary.html.jinja', context=context)
|
||||
|
||||
|
||||
def handle_dxf_file(session_manager, file_contents, filename=None, save_file=True):
|
||||
def handle_dxf_file(session_manager, file_contents, filename=None):
|
||||
errors = []
|
||||
user_values = session_manager.user_values()
|
||||
validator = FileValidator(user_values)
|
||||
@@ -215,8 +230,7 @@ def handle_dxf_file(session_manager, file_contents, filename=None, save_file=Tru
|
||||
DXFHelper(),
|
||||
SubarrayValidator())
|
||||
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
|
||||
if save_file:
|
||||
session_manager.save_uploaded_file(csv, dxf_file_name=filename)
|
||||
session_manager.save_uploaded_file(csv, dxf_file_name=filename)
|
||||
|
||||
buildings = dxf_data['buildings']
|
||||
session_manager.save_buildings_polygons(buildings)
|
||||
@@ -264,6 +278,8 @@ def array_summary():
|
||||
file_contents = validator.obtain_stream(file)
|
||||
success, errors = handle_dxf_file(session_manager, file_contents, filename=file.filename)
|
||||
if success:
|
||||
if is_sales_force_session():
|
||||
session['sf_session']['new_dxf_file'] = True
|
||||
return redirect(url_for('array_summary'))
|
||||
else:
|
||||
array_form.dxf_upload.errors.extend(errors)
|
||||
@@ -317,7 +333,10 @@ def array_summary():
|
||||
elif not context['site_data_available']:
|
||||
context['no_proceed'] = True
|
||||
|
||||
if is_sfdc_session() and 'dxf_link_loaded' not in session:
|
||||
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()
|
||||
@@ -326,14 +345,21 @@ def array_summary():
|
||||
|
||||
@app.route("/load_dxf/", methods=['GET', 'POST'])
|
||||
def load_dxf_file():
|
||||
if 'dxf_link' not in session:
|
||||
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['dxf_link']
|
||||
response = requests.get(dxf_link)
|
||||
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('/')
|
||||
@@ -341,12 +367,12 @@ def load_dxf_file():
|
||||
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, save_file=False)
|
||||
session['dxf_link_loaded'] = True
|
||||
success, errors = handle_dxf_file(session_manager, file_contents, filename=filename)
|
||||
session['sf_session']['dxf_link_loaded'] = True
|
||||
if success:
|
||||
return jsonify({'status': 'success'})
|
||||
return jsonify({'status': 'success', 'messages': ['DXF from Salesforce loaded successfully.']})
|
||||
else:
|
||||
errors = ['Unable to download DXF file from Sales Force ({})'.format(response.status_code)]
|
||||
errors = ['Unable to download DXF file from Salesforce ({})'.format(response.status_code)]
|
||||
|
||||
response = jsonify({'errors': errors})
|
||||
response.status_code = 400
|
||||
@@ -431,10 +457,11 @@ def download():
|
||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||
context = session_manager.context()
|
||||
context['current_step'] = 5
|
||||
error, data = session.pop('sfdc_export_urls', (None, None))
|
||||
if data is not None:
|
||||
context['sfdc_export_error'] = error
|
||||
context['sfdc_export_urls'] = data
|
||||
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()
|
||||
return render_template('download.html.jinja', context=context)
|
||||
|
||||
@@ -524,55 +551,83 @@ def helix_documentation():
|
||||
return render_template('helix_documentation.jinja', context=context)
|
||||
|
||||
|
||||
# Sales Force Integration
|
||||
# Salesforce Integration
|
||||
@app.route('/sales_force_login')
|
||||
def sales_force_login():
|
||||
# To test it locally: https://localhost:8443/sales_force_login?SFID=a3cL00000004QsQIAU
|
||||
sfid = request.args.get('SFID')
|
||||
# To test it locally: https://localhost:8443/sales_force_login?sfid=a3cL00000004QsQIAU
|
||||
sfid = request.args.get('sfid')
|
||||
if sfid:
|
||||
session.clear()
|
||||
session['SFID'] = sfid
|
||||
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, SFID=sfid), SFID=sfid)
|
||||
session['sf_session'] = {}
|
||||
session['sf_session']['sfid'] = sfid
|
||||
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, sfid=sfid), sfid=sfid)
|
||||
else:
|
||||
flash('Missing sfid')
|
||||
return redirect('/')
|
||||
|
||||
|
||||
@app.route('/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')
|
||||
|
||||
resp = sales_force.authorized_response()
|
||||
try:
|
||||
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:
|
||||
print('Unable to authenticate to SFDC.')
|
||||
flash('Unknown response from Salesforce.')
|
||||
print('Unknown response from Salesforce.')
|
||||
return redirect(next_url)
|
||||
|
||||
print('New Sales Force - OAuth2 login')
|
||||
print('New Salesforce - OAuth2 login')
|
||||
db_session = sql_constant.sql_session_maker()
|
||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||
session['sales_force_token'] = resp['access_token']
|
||||
session['sf_session']['access_token'] = resp['access_token']
|
||||
|
||||
data = sf_tasks.get_site_characterization_from_sales_force(session)
|
||||
if data:
|
||||
session['dxf_link'] = data['dxf_link']
|
||||
session_manager.save_form_submission(data)
|
||||
return redirect(next_url)
|
||||
else:
|
||||
helix_session_id = session['id']
|
||||
access_token = session['sf_session']['access_token']
|
||||
sfid = session['sf_session']['sfid']
|
||||
try:
|
||||
data, status_code = sf_tasks.get_site_characterization_from_sales_force(helix_session_id, access_token, sfid)
|
||||
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()
|
||||
|
||||
|
||||
@app.route("/export-sfdc")
|
||||
def export_sfdc():
|
||||
if not is_sfdc_session():
|
||||
if not is_sales_force_session():
|
||||
flash('This is not a Salesforce session')
|
||||
return redirect('/')
|
||||
|
||||
db_session = sql_constant.sql_session_maker()
|
||||
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
||||
helix_session_id = session_manager.session['id']
|
||||
access_token = session['sales_force_token']
|
||||
sfid = session['SFID']
|
||||
error, data = sf_tasks.export_to_sfdc(helix_session_id, access_token, sfid)
|
||||
sf_session = session['sf_session']
|
||||
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'] = session['dxf_link']
|
||||
session['sfdc_export_urls'] = (error, data)
|
||||
data['dxfUrlFromSF'] = sf_session['dxf_link']
|
||||
sf_session['export_urls'] = (error, data)
|
||||
db_session.close()
|
||||
return redirect('/download')
|
||||
|
||||
@@ -580,19 +635,55 @@ def export_sfdc():
|
||||
if sales_force:
|
||||
@sales_force.tokengetter
|
||||
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')
|
||||
def sales_force_logout():
|
||||
session.pop('SFID', None)
|
||||
session.pop('sales_force_token', None)
|
||||
session.pop('dxf_link', None)
|
||||
session.pop('dxf_link_loaded', None)
|
||||
session.pop('sfdc_export_urls', None)
|
||||
session.clear()
|
||||
session.pop('sf_session', None)
|
||||
return redirect('/')
|
||||
# End of Sales Force Integration
|
||||
# End of Salesforce 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')
|
||||
|
||||
@@ -10,4 +10,4 @@ class Corner(object):
|
||||
|
||||
@property
|
||||
def dictionary(self):
|
||||
return {"x": self.x, "y": self.y, "length ccw": self.length_ccw, "length cw : ": self.length_cw, "angle": self.angle}
|
||||
return {"x": self.x, "y": self.y, "length_ccw": self.length_ccw, "length_cw": self.length_cw, "angle": self.angle}
|
||||
|
||||
@@ -2,6 +2,7 @@ import sys
|
||||
from math import sqrt, degrees, acos
|
||||
from helix.models.corner import Corner
|
||||
from helix.constants.global_constants import max_corner_angle
|
||||
import flask_featureflags as feature
|
||||
|
||||
class ProjectPresenter(object):
|
||||
def __init__(self, system_type, module_type):
|
||||
@@ -88,11 +89,10 @@ class ProjectPresenter(object):
|
||||
# True if cross is positive
|
||||
# False if negative or zero
|
||||
return x1 * y2 > x2 * y1
|
||||
|
||||
|
||||
|
||||
|
||||
result = []
|
||||
for building in buildings:
|
||||
if feature.is_active('ff_cpp'):
|
||||
for building in buildings:
|
||||
presentable_building = []
|
||||
result.append(presentable_building)
|
||||
|
||||
@@ -112,7 +112,6 @@ class ProjectPresenter(object):
|
||||
if (cross_sign(x1, y1, x2, y2)) and (theta < max_corner_angle) :
|
||||
#print('Inner Angle')
|
||||
presentable_building.append(Corner(ref[0], ref[1], corner_length_ccw,corner_length_cw, theta).__dict__)
|
||||
|
||||
return result
|
||||
|
||||
def get_max_y(self,buildings, panels):
|
||||
|
||||
20
helix/qa_helper.py
Normal file
20
helix/qa_helper.py
Normal file
@@ -0,0 +1,20 @@
|
||||
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,6 +1,5 @@
|
||||
import io
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
|
||||
@@ -12,45 +11,50 @@ from helix.doc_gen_params_builder import DocGenParamsBuilder
|
||||
from helix.helpers.camel_case import convert_dict_keys_to_snake_case
|
||||
from helix.json_builder import JsonBuilder
|
||||
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.s3_helper import s3_upload
|
||||
from helix.session_manager import SessionManager
|
||||
|
||||
|
||||
def get_site_characterization_from_sales_force(session):
|
||||
access_token = session['sales_force_token']
|
||||
sfid = session['SFID']
|
||||
helix_id = session['id']
|
||||
def get_site_characterization_from_sales_force(helix_session_id, access_token, sfid):
|
||||
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
|
||||
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
|
||||
headers = {'Authorization': 'Bearer {}'.format(access_token)}
|
||||
result = requests.get(url, headers=headers, params={'SFID': sfid, 'helix_session_id': helix_id})
|
||||
params = {'sfid': sfid, 'helix_session_id': helix_session_id}
|
||||
result = requests.get(url, headers=headers, params=params, timeout=30)
|
||||
if result.status_code == 200:
|
||||
data = result.json()
|
||||
if data:
|
||||
data = convert_sales_force_data_format_to_helix(data)
|
||||
return data
|
||||
return data, 200
|
||||
else:
|
||||
print('Error while getting data from Sales Force: {}'.format(result.status_code))
|
||||
print('Error while getting data from Salesforce ({})'.format(result.status_code))
|
||||
print(url, params)
|
||||
print(result.content)
|
||||
# print(result.request.headers)
|
||||
return None, result.status_code
|
||||
|
||||
|
||||
def convert_sales_force_data_format_to_helix(data):
|
||||
data = convert_dict_keys_to_snake_case(data)
|
||||
|
||||
if data['system_type'] == 'Single-Tilt':
|
||||
if data['system_type'] in ('Single-Tilt', 'Single Tilt'):
|
||||
data['system_type'] = SystemType.singleTilt.value
|
||||
elif data['system_type'] == 'Dual-Tilt':
|
||||
elif data['system_type'] == ('Dual-Tilt', 'Dual Tilt'):
|
||||
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['max_system_pressure'] = data['max_psf'] = data['system_pressure']
|
||||
return data
|
||||
# data['spectral_response_acceleration']
|
||||
|
||||
|
||||
def export_to_sfdc(helix_session_id, access_token, sfid):
|
||||
step = 'Exporting to SFDC'
|
||||
def export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=False, qa_test=None):
|
||||
step = 'Exporting to Salesforce'
|
||||
try:
|
||||
# 1. Load User Values
|
||||
step = 'Loading User Values'
|
||||
@@ -83,36 +87,43 @@ def export_to_sfdc(helix_session_id, access_token, sfid):
|
||||
|
||||
# 5. Save CSV/PDF/DXF files into AWS-S3
|
||||
step = 'Uploading to S3'
|
||||
filename = uuid.uuid4().hex
|
||||
bom_csv_url = s3_upload(io.StringIO(csv_file), filename=filename + '.csv')
|
||||
doc_url = s3_upload(io.BytesIO(document), filename=filename + '.pdf')
|
||||
bom_json_url = s3_upload(io.StringIO(bom_list_json), filename=filename + '.json')
|
||||
if dxf_contents: # Optional
|
||||
dxf_url = s3_upload(io.StringIO(dxf_contents), filename=filename + '.dxf')
|
||||
if qa_test == QAScenario.SF_ERROR_UPLOAD_S3.value:
|
||||
raise QAException(qa_test)
|
||||
|
||||
bom_csv_url = s3_upload(io.StringIO(csv_file), filename=sfid + '-csv.csv')
|
||||
doc_url = s3_upload(io.BytesIO(document), filename=sfid + '-pdf.pdf')
|
||||
bom_json_url = s3_upload(io.StringIO(bom_list_json), filename=sfid + '-json.json')
|
||||
# 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:
|
||||
dxf_url = None
|
||||
|
||||
# 6. Notify SFDC system with an API request
|
||||
step = 'Notifying SFDC'
|
||||
step = 'Notifying Salesforce'
|
||||
if qa_test == QAScenario.SF_ERROR_NOTIFY_SF.value:
|
||||
raise QAException(qa_test)
|
||||
|
||||
data = {
|
||||
'dxfUrl': dxf_url,
|
||||
'bomCsvUrl': bom_csv_url,
|
||||
'documentationUrl': doc_url,
|
||||
'bom': bom_list,
|
||||
'helixSessionId': helix_session_id,
|
||||
'SFID': sfid,
|
||||
'sfid': sfid,
|
||||
}
|
||||
headers = {'Authorization': 'Bearer {}'.format(access_token)}
|
||||
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
|
||||
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
|
||||
result = requests.post(url, headers=headers, data=data, timeout=30)
|
||||
result = requests.post(url, headers=headers, json=data, timeout=30)
|
||||
# 7. Internal logs
|
||||
if result.status_code <= 299:
|
||||
print('Sales Force notified successfully for session {}.'.format(helix_session_id))
|
||||
print('Salesforce notified successfully for session {}.'.format(helix_session_id))
|
||||
# print(result.content)
|
||||
error = None
|
||||
else:
|
||||
error = 'Helix wasn\'t able to notify the Sales Force ({})'.format(result.status_code)
|
||||
error = 'Helix Calculator was not able to notify Salesforce ({})'.format(result.status_code)
|
||||
|
||||
print(error)
|
||||
print(result.content)
|
||||
|
||||
|
||||
@@ -72,6 +72,12 @@ i.icon-info-circled {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.info_message {
|
||||
color: black;
|
||||
padding-left: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error_message {
|
||||
color: red;
|
||||
padding-left: 5px;
|
||||
|
||||
@@ -81,17 +81,17 @@ class SessionManager(object):
|
||||
building_height=form_data.get('building_height', 0),
|
||||
building_width=form_data.get('building_width', 0),
|
||||
building_length=form_data.get('building_length', 0),
|
||||
parapet_height=form_data.get('building_parapet_height', 0),
|
||||
parapet_height=form_data.get('parapet_height', form_data.get('building_parapet_height', 0)),
|
||||
wind_speed=form_data.get('wind_speed', 0),
|
||||
exposure_category=form_data.get('exposure_category', 'C'),
|
||||
exposure_transition_distance=form_data.get('exposure_category_transition_distance', 0),
|
||||
ballast_block_weight=form_data.get('ballast_block_weight', 0),
|
||||
max_psf=form_data.get('max_system_pressure', 0),
|
||||
ballast_block_weight=form_data.get('ballast_block_weight', form_data.get('ballast_weight', 0)),
|
||||
max_psf=form_data.get('system_pressure', form_data.get('max_system_pressure', form_data.get('max_psf', 0))),
|
||||
system_type=form_data.get('system_type', SystemType.dualTilt.value),
|
||||
module_type=form_data.get('module_type', ModuleType.Cell96.value),
|
||||
anchor_type=form_data.get('anchor_type', AnchorType.OMG_PowerGrip_Plus.value),
|
||||
spectral_response=form_data.get('design_spectral_response', 1),
|
||||
seismic_importance_factor=form_data.get('importance_factor', 1)
|
||||
spectral_response=form_data.get('design_spectral_response', form_data.get('spectral_response_acceleration', form_data.get('spectral_response', 1))),
|
||||
seismic_importance_factor=form_data.get('seismic_importance_factor', form_data.get('importance_factor', 1))
|
||||
)
|
||||
self.db_session.add(self.site)
|
||||
self.db_session.commit()
|
||||
|
||||
@@ -616,13 +616,20 @@ i.icon-info-circled {
|
||||
}
|
||||
|
||||
/* line 75 */
|
||||
.info_message {
|
||||
color: black;
|
||||
padding-left: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* line 81 */
|
||||
.error_message {
|
||||
color: red;
|
||||
padding-left: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* line 81 */
|
||||
/* line 87 */
|
||||
input, select {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
@@ -633,7 +640,7 @@ input, select {
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* line 91 */
|
||||
/* line 97 */
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
@@ -641,14 +648,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);
|
||||
}
|
||||
|
||||
/* line 98 */
|
||||
/* line 104 */
|
||||
.submit, .download {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #EAEAEA;
|
||||
}
|
||||
|
||||
/* line 104 */
|
||||
/* line 110 */
|
||||
.button, button {
|
||||
background: #5199F5;
|
||||
color: white;
|
||||
@@ -660,7 +667,7 @@ select {
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* line 115 */
|
||||
/* line 121 */
|
||||
.navigation_buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -668,18 +675,18 @@ select {
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
padding: 20px;
|
||||
}
|
||||
/* line 122 */
|
||||
/* line 128 */
|
||||
.navigation_buttons a {
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
margin: 0 20px;
|
||||
}
|
||||
/* line 128 */
|
||||
/* line 134 */
|
||||
.navigation_buttons .button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* line 133 */
|
||||
/* line 139 */
|
||||
.back {
|
||||
border: 2px solid #5199F5;
|
||||
color: #5199F5;
|
||||
@@ -687,12 +694,12 @@ select {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* line 140 */
|
||||
/* line 146 */
|
||||
a.back {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* line 144 */
|
||||
/* line 150 */
|
||||
.sample_images {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -700,18 +707,18 @@ a.back {
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
padding: 15px;
|
||||
}
|
||||
/* line 151 */
|
||||
/* line 157 */
|
||||
.sample_images a {
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
margin: 2px 5px;
|
||||
}
|
||||
/* line 157 */
|
||||
/* line 163 */
|
||||
.sample_images .button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* line 162 */
|
||||
/* line 168 */
|
||||
.image_button, button {
|
||||
background: #5199F5;
|
||||
color: white;
|
||||
|
||||
@@ -2,21 +2,29 @@
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#sf_msg_container").empty();
|
||||
$("#sf_msg_container").show();
|
||||
$('#sf-spinner-panel').css('width', '100%'); // Workaround for Safari issue
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/load_dxf/'
|
||||
}).done(function(r) {
|
||||
console.log(r)
|
||||
$("#sf_msg_container").append('<li class="">DXF from Sales Force loaded successfully.</li>');
|
||||
setTimeout(function() { window.location.reload() }, 500);
|
||||
}).fail(function(r) {
|
||||
// console.log(r.status)
|
||||
r.responseJSON.errors.forEach((msg) => {
|
||||
$("#sf_msg_container").append('<li class="error_message">' + msg + '</li>');
|
||||
})
|
||||
}).always(function() {
|
||||
}).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);
|
||||
inverterStringsPerInverterForm.css('display', 'inherit');
|
||||
inverterStringsPerInverterForm.css('display', 'block');
|
||||
} else {
|
||||
inverterStringsPerInverter.append($('<option selected value="0"></option>'));
|
||||
inverterStringsPerInverterForm.css('display', 'none');
|
||||
@@ -160,7 +160,8 @@
|
||||
if (data) {
|
||||
$('#standalone_inverter_id').val(uuid);
|
||||
$('#standalone_ac_run_length').val(data['ac_run_length']);
|
||||
$('#attachment-point').val(data['attachment_point'][0]);
|
||||
var attach_point = data['attachment_point'][1];
|
||||
$('#attachment_point option[value=' + attach_point + ']').attr('selected','selected');
|
||||
|
||||
fillInverterData(data, '#inverter');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% set title = "Helix Calculator" %}
|
||||
{% block contents %}
|
||||
<script>
|
||||
{%if context['corners']%}
|
||||
{%if context['corners'] and 'ff_cpp' is active_feature %}
|
||||
var corners_data = {{ context['corners']|tojson}};
|
||||
if (corners_data){
|
||||
console.log("Corners data :");
|
||||
@@ -20,12 +20,12 @@
|
||||
<i class="icon-spin6 animate-spin"></i>
|
||||
</div>
|
||||
|
||||
{% if 'SFID' in session %}
|
||||
{% 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 Sales Force. Please wait, this may take a while.</p>
|
||||
<p>Loading DXF file from Salesforce. Please wait, this may take a while.</p>
|
||||
<i class="icon-spin6 animate-spin"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
{% if context['sfdc_export_error'] %}
|
||||
<div class="summary_warning" style="margin-top: 20px;">{{ context['sfdc_export_error'] }}</div>
|
||||
{% else %}
|
||||
<div class="msg-container">Documents exported to Sales Force succesffully.</div>
|
||||
<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 Sales Force)</li>
|
||||
<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>
|
||||
@@ -40,7 +40,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if 'SFID' in session %}
|
||||
{% if 'sf_session' in session %}
|
||||
<div class="download">
|
||||
<a href="/export-sfdc">
|
||||
<button>Export documents to Salesforce</button>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<div>Inverter Brand</div>
|
||||
</div>
|
||||
<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 }}
|
||||
{% for field in inverter_brand_form.group('hidden') %}
|
||||
{{ field }}
|
||||
|
||||
@@ -59,7 +59,23 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</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 %}
|
||||
|
||||
{% if 'sf_session' in session %}
|
||||
<small><i>Salesforce project</i> (<a href="/sales_force_logout">Log out</a>)</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
|
||||
|
||||
@@ -87,7 +87,4 @@
|
||||
|
||||
{% endif %}
|
||||
{% 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 %}
|
||||
|
||||
@@ -2,7 +2,7 @@ nose==1.3.7
|
||||
selenium==2.53.1
|
||||
splinter==0.7.3
|
||||
cssselect==0.9.1
|
||||
lxml==3.6.0
|
||||
lxml==4.1.1
|
||||
mockredispy==2.9.0.11
|
||||
Flask-Testing==0.4.2
|
||||
splinter[flask]
|
||||
|
||||
@@ -23,3 +23,5 @@ blinker==1.4
|
||||
Flask-OAuthlib==0.9.4
|
||||
boto3==1.4.8
|
||||
ujson==1.35
|
||||
Flask-FeatureFlags==0.6
|
||||
mock==2.0.0
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
python-3.5.1
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
from numpy.testing import assert_array_equal, assert_equal
|
||||
from helix.constants.module_type import ModuleType
|
||||
@@ -9,11 +10,15 @@ from helix.models.subarray import Subarray
|
||||
from helix.presenters.panel_presenter import ProjectPresenter
|
||||
from helix.constants.system_type import SystemType
|
||||
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):
|
||||
|
||||
|
||||
def test_get_table_data_single_tilt_96cell(self):
|
||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||
panels = [
|
||||
@@ -88,7 +93,7 @@ class PanelPresenterTest(unittest.TestCase):
|
||||
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(self):
|
||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||
buildings = [ [ [0, 0], [60, 0], [60,60], [0, 60] ] ] # big square
|
||||
@@ -101,6 +106,7 @@ class PanelPresenterTest(unittest.TestCase):
|
||||
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
|
||||
@@ -113,6 +119,7 @@ class PanelPresenterTest(unittest.TestCase):
|
||||
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]] ]
|
||||
@@ -125,6 +132,7 @@ class PanelPresenterTest(unittest.TestCase):
|
||||
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] ] ]
|
||||
|
||||
@@ -105,3 +105,7 @@ def assert_image_equal(image_1, image_2, error=5e-2):
|
||||
|
||||
average_error = total_error / pixels
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user