diff --git a/.cspell.json b/.cspell.json index 398795018a..f74492eb55 100644 --- a/.cspell.json +++ b/.cspell.json @@ -25,6 +25,7 @@ "auditlog", "auth", "autogenerated", + "automodule", "aws", "AWS", "backend", @@ -54,6 +55,7 @@ "casted", "cb", "CEJST", + "celerybeat", "cfg", "changelog", "checkbox", diff --git a/.gitignore b/.gitignore index 166547d8dc..a670635509 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,18 @@ -.DS_Store .cache +.coverage +.cspell.txt +.DS_Store .env .hypothesis .idea .project .pydevproject .python-version -.coverage .vscode coverage.protractor.json -.vscode *~ *~$* +*.ipynb *.pyc *.swp *.swo @@ -25,7 +26,6 @@ pkgs/newrelic-1\.6\.0\.13\.tar\.gz chromedriver.log ipython_input_log_history .ipython_input_log_history -*.ipynb **/ipython_input_log_history web_root/csvs/* web_root/uploads/* diff --git a/LICENSE.md b/LICENSE.md index e03bc2187b..45d140df16 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -SEED Platform™, Copyright (c) 2017, 2023 Alliance for Sustainable Energy, LLC, and other contributors. +SEED Platform™, Copyright (c) 2017, 2024 Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted diff --git a/bin/protractor_start_server.sh b/bin/protractor_start_server.sh index c576cb64d4..3c25e1fb7e 100755 --- a/bin/protractor_start_server.sh +++ b/bin/protractor_start_server.sh @@ -18,8 +18,6 @@ echo "run e2e tests" ./node_modules/protractor/bin/protractor seed/static/seed/tests/protractor-tests/protractorConfigCoverage.js # echo "install coverall merge stuffs" # gem install coveralls-lcov -# pip install coveralls-merge # echo "run lcov to coveralls json" # coveralls-lcov -v -n protractorReports/lcov.info > coverage.protractor.json -# echo "merge and post coveralls" -# coveralls-merge coverage.protractor.json +# echo "upload to coveralls" diff --git a/docs/source/conf.py b/docs/source/conf.py index 5c77244de8..57768e6b56 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -56,7 +56,9 @@ templates_path = ['_templates'] # Location of word list. -spelling_word_list_filename = '../../.cspell.json' +# convert the spelling list to a text file and save +open('../../.cspell.txt', 'w').write('\n'.join(json.load(open('../../.cspell.json'))['words'])) +spelling_word_list_filename = '../../.cspell.txt' # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: @@ -71,7 +73,7 @@ # General information about the project. project = 'SEED Platform' -copyright = '2017, 2023, Alliance for Sustainable Energy, LLC, and other contributors.' +copyright = '2017, 2024, Alliance for Sustainable Energy, LLC, and other contributors.' author = 'Alliance for Sustainable Energy, LLC, and other contributors.' # The version info for the project you're documenting, acts as replacement for @@ -91,7 +93,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/source/docker.rst b/docs/source/docker.rst index 60db652e50..a0208c345b 100644 --- a/docs/source/docker.rst +++ b/docs/source/docker.rst @@ -110,7 +110,7 @@ Ubuntu server 18.04 or newer with a m5ad.xlarge (if using in Production instance Deploying with Docker -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^ The preferred way to deploy with Docker is using docker swarm and docker stack. Look at the `deploy.sh script`_ for implementation details. diff --git a/docs/source/kubernetes_deployment.rst b/docs/source/kubernetes_deployment.rst index a67dc9ab44..3f70fdc154 100644 --- a/docs/source/kubernetes_deployment.rst +++ b/docs/source/kubernetes_deployment.rst @@ -59,14 +59,14 @@ Helm Helm organizes all of your Kubernetes deployment, service, and volume yml files into "charts" that can be deployed, managed, and published with simple commands. To install Helm: -* `Windows eksctl https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-windows`_ +* `Windows eksctl `_ * Mac (with Homebrew) :code:`brew install helm` EKS Control (AWS Specific) ^^^^^^^^^^^^^^^^^^^^^^^^^^ EKSCtl is a command line tool to manage Elastic Kubernetes clusters on AWS. If not using AWS, then disregard this section. -* `Windows `_ +* `Windows eksctl config `_ * Mac (with Homebrew) :code:`brew install eksctl` To launch a cluster on using EKSCts, run the following command in the terminal (assuming adequate permissions for the user). Also make sure to replace items in the `<>` brackets. @@ -161,7 +161,7 @@ This chart contains the deployment specification for the Celery container to con value: # must match db-postgres-deployment.yaml and web-celery-deployment.yaml bsyncr-deployment.yaml -************************** +********************** This chart contains the deployment specification for the bsyncr analysis server. Request a NOAA token from `this website `_. .. code-block:: yaml @@ -239,7 +239,7 @@ The command below will restart the pods and re-pull the docker images. Other Resources --------------- -Common kubectl actions can be found `here `_ +Common kubectl actions can be found `on the kubernetes website `_ .. _AWS: https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html diff --git a/docs/source/migrations.rst b/docs/source/migrations.rst index cd321bd2ba..624a23c329 100644 --- a/docs/source/migrations.rst +++ b/docs/source/migrations.rst @@ -223,9 +223,7 @@ Max OSX Version 2.5.2 ------------- -- There are no manual migrations that are needed. The `./manage.py migrate` command may take awhile -to run since the migration requires the recalculation of all the normalized addresses to parse -bldg correct and to cast the result as a string and not a bytestring. +- There are no manual migrations that are needed. The `./manage.py migrate` command may take awhile to run since the migration requires the recalculation of all the normalized addresses to parse bldg correct and to cast the result as a string and not a bytestring. Version 2.5.1 ------------- diff --git a/docs/source/modules/seed.rst b/docs/source/modules/seed.rst index a8c3166a76..771c0324c2 100644 --- a/docs/source/modules/seed.rst +++ b/docs/source/modules/seed.rst @@ -80,14 +80,6 @@ Token Generator :undoc-members: :show-inheritance: -URLs ----- - -.. automodule:: seed.urls - :members: - :undoc-members: - :show-inheritance: - Utils ----- diff --git a/docs/source/modules/seed.utils.rst b/docs/source/modules/seed.utils.rst index 813f849f94..27fd9b6cf7 100644 --- a/docs/source/modules/seed.utils.rst +++ b/docs/source/modules/seed.utils.rst @@ -20,22 +20,6 @@ Buildings :undoc-members: :show-inheritance: -Constants ---------- - -.. automodule:: seed.utils.constants - :members: - :undoc-members: - :show-inheritance: - -Mappings --------- - -.. automodule:: seed.utils.mapping - :members: - :undoc-members: - :show-inheritance: - Organizations ------------- diff --git a/requirements/test.txt b/requirements/test.txt index 191964657e..7808659ee7 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -11,29 +11,28 @@ psutil==5.6.7 # python testing Faker==0.9.3 mock==2.0.0 -coveralls-merge==0.0.3 vcrpy==4.2.1 -pytest==7.2.0 -pytest-django==4.5.2 +pytest==7.4.4 +pytest-django==4.7.0 # Lock urllib3 to v1 until vcrpy supports it urllib3<2 # static code analysis -flake8==3.8.1 -pycodestyle==2.6.0 -pre-commit==2.19.0 +flake8==7.0.0 +pycodestyle==2.11.1 +pre-commit==3.6.0 # documentation and spelling -Sphinx==4.0.2 -sphinxcontrib-spelling==4.3.0 -sphinx_rtd_theme==0.4.3 -docutils==0.17.1 +Sphinx==7.2.6 +sphinxcontrib-spelling==8.0.0 +sphinx_rtd_theme==2.0.0 +docutils==0.20.1 # property-based testing -hypothesis==6.12.0 +hypothesis==6.94.0 # For running the server -uWSGI==2.0.22; sys_platform != "win32" +uWSGI==2.0.23; sys_platform != "win32" # static type checking -mypy==1.0.0 +mypy==1.8.0 diff --git a/seed/audit_template/audit_template.py b/seed/audit_template/audit_template.py index 80a97cb96e..b822640877 100644 --- a/seed/audit_template/audit_template.py +++ b/seed/audit_template/audit_template.py @@ -59,24 +59,23 @@ def get_submission(self, audit_template_submission_id: int, report_format: str = Args: audit_template_submission_id (int): value of the "Submission ID" as seen on Audit Template - report_format (str, optional): Report format, either `xml` or `pdf`. Defaults to 'pdf'. + report_format (str, optional): Report format, either `json`, `xml`, or `pdf`. Defaults to 'pdf'. Returns: requests.response: Result from Audit Template website """ - # supporting 'PDF' and 'XML' formats only for now + # supporting 'JSON', PDF', and 'XML' formats only for now token, message = self.get_api_token() if not token: return None, message # validate format - if report_format.lower() not in ['xml', 'pdf']: + if report_format.lower() not in ['json', 'xml', 'pdf']: report_format = 'pdf' # set headers - headers = {'accept': 'application/pdf'} - if report_format.lower() == 'xml': - headers = {'accept': 'application/xml'} + accept_type = 'application/' + report_format.lower() + headers = {'accept': accept_type} url = f'{self.API_URL}/rp/submissions/{audit_template_submission_id}.{report_format}?token={token}' try: diff --git a/seed/data_importer/meters_parser.py b/seed/data_importer/meters_parser.py index f7f701d5dc..743646aac8 100644 --- a/seed/data_importer/meters_parser.py +++ b/seed/data_importer/meters_parser.py @@ -461,11 +461,11 @@ class with the data. # check which (if any) meter readings are provided # there can be more than one reading type per row (e.g., both electricity # and natural gas in the same row) - provided_reading_types = [] + provided_reading_types = set() for field in raw_data[0].keys(): for header_string in Meter.ENERGY_TYPE_BY_HEADER_STRING.keys(): if field.startswith(header_string): - provided_reading_types.append(field) + provided_reading_types.add(field) continue if not provided_reading_types: diff --git a/seed/migrations/0211_auto_20240109_1348.py b/seed/migrations/0211_auto_20240109_1348.py new file mode 100644 index 0000000000..ad0266c223 --- /dev/null +++ b/seed/migrations/0211_auto_20240109_1348.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.23 on 2024-01-09 21:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seed', '0210_natural_sort'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='derivedcolumn', + name='unique_name_for_organization', + ), + migrations.AddConstraint( + model_name='derivedcolumn', + constraint=models.UniqueConstraint(fields=('organization', 'name', 'inventory_type'), name='unique_name_for_organization'), + ), + ] diff --git a/seed/migrations/0211_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py b/seed/migrations/0212_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py similarity index 99% rename from seed/migrations/0211_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py rename to seed/migrations/0212_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py index cb24189939..1ca73ea115 100644 --- a/seed/migrations/0211_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py +++ b/seed/migrations/0212_auto_add_props_to_tree_squashed_0213_analysis_access_level_instance.py @@ -54,7 +54,7 @@ def set_analysis_ali(apps, schema_editor): replaces = [('seed', '0211_auto_add_props_to_tree'), ('seed', '0212_auto_20230623_1556'), ('seed', '0213_analysis_access_level_instance')] dependencies = [ - ('seed', '0210_natural_sort'), + ('seed', '0211_auto_20240109_1348'), ('orgs', '0029_auto_20230413_1250'), ] diff --git a/seed/models/derived_columns.py b/seed/models/derived_columns.py index 86c92f729c..598ffa6d67 100644 --- a/seed/models/derived_columns.py +++ b/seed/models/derived_columns.py @@ -202,7 +202,7 @@ class DerivedColumn(models.Model): class Meta: constraints = [ models.UniqueConstraint( - fields=['organization', 'name'], name='unique_name_for_organization' + fields=['organization', 'name', 'inventory_type'], name='unique_name_for_organization' ) ] diff --git a/seed/serializers/meter_readings.py b/seed/serializers/meter_readings.py index b4f9befcd9..730e820db0 100644 --- a/seed/serializers/meter_readings.py +++ b/seed/serializers/meter_readings.py @@ -7,6 +7,7 @@ from typing import Tuple import dateutil.parser +from django.core.exceptions import ValidationError from django.db import connection from django.utils.timezone import make_aware from psycopg2.extras import execute_values @@ -54,6 +55,17 @@ def create(self, validated_data) -> list[MeterReading]: return updated_readings + def validate(self, data): + # duplicate start and end date pairs will cause sql errors + date_pairs = set() + for datum in data: + date_pair = (datum.get('start_time'), datum.get('end_time')) + if date_pair in date_pairs: + raise ValidationError('Error: Each reading must have a unique combination of start_time end end_time.') + date_pairs.add(date_pair) + + return data + class MeterReadingSerializer(serializers.ModelSerializer): class Meta: @@ -95,7 +107,6 @@ def create(self, validated_data) -> MeterReading: # Convert tuple to MeterReading for response updated_reading = MeterReading(**{field: result[i] for i, field in enumerate(meter_fields)}) - return updated_reading def to_representation(self, obj): diff --git a/seed/static/seed/js/controllers/insights_program_controller.js b/seed/static/seed/js/controllers/insights_program_controller.js index 9f9cf1b912..6174c14261 100644 --- a/seed/static/seed/js/controllers/insights_program_controller.js +++ b/seed/static/seed/js/controllers/insights_program_controller.js @@ -12,16 +12,36 @@ angular.module('BE.seed.controller.insights_program', []).controller('insights_p 'compliance_metric_service', 'spinner_utility', 'organization_payload', + 'filter_groups', 'cycles', + 'property_columns', 'auth_payload', // eslint-disable-next-line func-names - function ($scope, $stateParams, $state, $uibModal, urls, compliance_metrics, compliance_metric_service, spinner_utility, organization_payload, cycles, auth_payload) { + function ( + $scope, + $stateParams, + $state, + $uibModal, + urls, + compliance_metrics, + compliance_metric_service, + spinner_utility, + organization_payload, + filter_groups, + cycles, + property_columns, + auth_payload + ) { $scope.id = $stateParams.id; $scope.cycles = cycles.cycles; $scope.organization = organization_payload.organization; $scope.initialize_chart = true; $scope.auth = auth_payload.auth; + // used by modal + $scope.filter_groups = filter_groups; + $scope.property_columns = property_columns; + // compliance metric $scope.compliance_metric = {}; $scope.compliance_metrics = compliance_metrics; @@ -44,6 +64,53 @@ angular.module('BE.seed.controller.insights_program', []).controller('insights_p // CHARTS const colors = { compliant: '#77CCCB', 'non-compliant': '#A94455', unknown: '#DDDDDD' }; + // Program Setup Modal + $scope.open_program_setup_modal = () => { + const modalInstance = $uibModal.open({ + templateUrl: `${urls.static_url}seed/partials/program_setup.html`, + controller: 'program_setup_controller', + size: 'lg', + backdrop: 'static', + resolve: { + cycles: () => $scope.cycles, + compliance_metrics: () => $scope.compliance_metrics, + organization_payload: () => $scope.organization, + filter_groups: () => $scope.filter_groups, + property_columns: () => $scope.property_columns, + id: () => $scope.selected_metric + } + }); + // on modal close + modalInstance.result.then((program) => { + // re-fetch compliance metrics + compliance_metric_service.get_compliance_metrics($scope.organization.id).then((data) => { + $scope.compliance_metrics = data; + // change selection to last selected in modal and reload + if ($scope.compliance_metrics.length > 0) { + if (program != null) { + $scope.compliance_metric = $scope.compliance_metrics.find((cm) => cm.id === program.id); + $scope.selected_metric = program.id; + } else { + // attempt to keep the selected metric + $scope.compliance_metric = $scope.compliance_metrics.find((cm) => cm.id === $scope.selected_metric); + if ($scope.compliance_metric == null) { + // load first metric b/c selected metric no longer exists + $scope.compliance_metric = $scope.compliance_metrics[0]; + $scope.selected_metric = $scope.compliance_metric.id; + } + } + } else { + // load nothing + $scope.compliance_metric = {}; + $scope.selected_metric = null; + $scope.data = null; + } + + $scope.updateSelectedMetric(); + }); + }); + }; + const _load_datasets = () => { // load data @@ -173,7 +240,10 @@ angular.module('BE.seed.controller.insights_program', []).controller('insights_p }; $scope.updateSelectedMetric = () => { - $scope.compliance_metric = _.find($scope.compliance_metrics, (o) => o.id === $scope.selected_metric); + $scope.compliance_metric = {}; + if ($scope.selected_metric != null) { + $scope.compliance_metric = _.find($scope.compliance_metrics, (o) => o.id === $scope.selected_metric); + } // reload data for selected metric _load_data(); diff --git a/seed/static/seed/js/controllers/insights_property_controller.js b/seed/static/seed/js/controllers/insights_property_controller.js index 9abcc9907a..19fcaceaab 100644 --- a/seed/static/seed/js/controllers/insights_property_controller.js +++ b/seed/static/seed/js/controllers/insights_property_controller.js @@ -11,20 +11,42 @@ angular.module('BE.seed.controller.insights_property', []).controller('insights_ 'compliance_metrics', 'compliance_metric_service', 'organization_payload', + 'filter_groups', + 'property_columns', + 'cycles', 'spinner_utility', 'auth_payload', // eslint-disable-next-line func-names - function ($scope, $state, $stateParams, $uibModal, urls, compliance_metrics, compliance_metric_service, organization_payload, spinner_utility, auth_payload) { + function ( + $scope, + $state, + $stateParams, + $uibModal, + urls, + compliance_metrics, + compliance_metric_service, + organization_payload, + filter_groups, + property_columns, + cycles, + spinner_utility, + auth_payload + ) { $scope.id = $stateParams.id; $scope.static_url = urls.static_url; $scope.organization = organization_payload.organization; $scope.auth = auth_payload.auth; + // used by modal + $scope.filter_groups = filter_groups; + $scope.property_columns = property_columns; + $scope.all_cycles = cycles.cycles; + // toggle help $scope.show_help = false; $scope.toggle_help = () => { $scope.show_help = !$scope.show_help; - } + }; // configs ($scope.configs set to saved_configs where still applies. // for example, if saved_configs.compliance_metric is 1, but 1 has been deleted, it does apply.) @@ -73,6 +95,53 @@ angular.module('BE.seed.controller.insights_property', []).controller('insights_ $scope.y_axis_options = []; $scope.x_categorical = false; + // Program Setup Modal + $scope.open_program_setup_modal = () => { + const modalInstance = $uibModal.open({ + templateUrl: `${urls.static_url}seed/partials/program_setup.html`, + controller: 'program_setup_controller', + size: 'lg', + backdrop: 'static', + resolve: { + cycles: () => $scope.all_cycles, + compliance_metrics: () => $scope.compliance_metrics, + organization_payload: () => $scope.organization, + filter_groups: () => $scope.filter_groups, + property_columns: () => $scope.property_columns, + id: () => $scope.selected_metric + } + }); + // on modal close + modalInstance.result.then((program) => { + // re-fetch compliance metrics + compliance_metric_service.get_compliance_metrics($scope.organization.id).then((data) => { + $scope.compliance_metrics = data; + // change selection to last selected in modal and reload + if ($scope.compliance_metrics.length > 0) { + if (program != null) { + $scope.configs.compliance_metric = $scope.compliance_metrics.find((cm) => cm.id === program.id); + $scope.selected_metric = program.id; + } else { + // attempt to keep the selected metric + $scope.configs.compliance_metric = $scope.compliance_metrics.find((cm) => cm.id === $scope.selected_metric); + if ($scope.configs.compliance_metric == null) { + // load first metric b/c selected metric no longer exists + $scope.configs.compliance_metric = $scope.compliance_metrics[0]; + $scope.selected_metric = $scope.configs.compliance_metric.id; + } + } + } else { + // load nothing + $scope.configs.compliance_metric = {}; + $scope.selected_metric = null; + $scope.data = null; + } + + $scope.update_metric(); + }); + }); + }; + $scope.$watch( 'configs', (new_configs) => { @@ -89,6 +158,7 @@ angular.module('BE.seed.controller.insights_property', []).controller('insights_ const _load_data = () => { if (_.isEmpty($scope.configs.compliance_metric)) { spinner_utility.hide(); + $scope.data = null; return; } spinner_utility.show(); @@ -189,7 +259,10 @@ angular.module('BE.seed.controller.insights_property', []).controller('insights_ spinner_utility.show(); // compliance metric - $scope.configs.compliance_metric = _.find($scope.compliance_metrics, (o) => o.id === $scope.selected_metric); + $scope.configs.compliance_metric = {}; + if ($scope.selected_metric != null) { + $scope.configs.compliance_metric = _.find($scope.compliance_metrics, (o) => o.id === $scope.selected_metric); + } // reload data for selected metric _load_data(); diff --git a/seed/static/seed/js/controllers/inventory_list_controller.js b/seed/static/seed/js/controllers/inventory_list_controller.js index 10b548796a..77a153c7e1 100644 --- a/seed/static/seed/js/controllers/inventory_list_controller.js +++ b/seed/static/seed/js/controllers/inventory_list_controller.js @@ -11,6 +11,7 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li '$state', '$stateParams', '$q', + '$timeout', 'inventory_service', 'label_service', 'data_quality_service', @@ -45,6 +46,7 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li $state, $stateParams, $q, + $timeout, inventory_service, label_service, data_quality_service, @@ -1860,7 +1862,7 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li fastWatch: true, flatEntityAccess: true, gridMenuShowHideColumns: false, - showTreeExpandNoChildren: false, + hidePinRight: true, saveFocus: false, saveGrouping: false, saveGroupingExpandedStates: false, @@ -1871,6 +1873,7 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li saveTreeView: false, saveVisible: false, saveWidths: false, + showTreeExpandNoChildren: false, useExternalFiltering: true, useExternalSorting: true, columnDefs: $scope.columns, @@ -1934,7 +1937,24 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li } }, 1000) ); - gridApi.pinning.on.columnPinned($scope, saveSettings); + gridApi.pinning.on.columnPinned($scope, (colDef, container) => { + if (container) { + saveSettings(); + } else { + // Hack to fix disappearing filter after unpinning a column + const gridCol = gridApi.grid.columns.find(({ colDef: { name } }) => name === colDef.name); + if (gridCol) { + gridCol.colDef.visible = false; + gridApi.grid.refresh(); + + $timeout(() => { + gridCol.colDef.visible = true; + gridApi.grid.refresh(); + saveSettings(); + }, 0); + } + } + }); const selectionChanged = () => { const selected = gridApi.selection.getSelectedRows(); diff --git a/seed/static/seed/js/controllers/inventory_map_controller.js b/seed/static/seed/js/controllers/inventory_map_controller.js index bbfa869081..3b17c9e9b0 100644 --- a/seed/static/seed/js/controllers/inventory_map_controller.js +++ b/seed/static/seed/js/controllers/inventory_map_controller.js @@ -44,17 +44,24 @@ angular.module('BE.seed.controller.inventory_map', []).controller('inventory_map }; const chunk = 250; - const fetchRecords = (fn, page = 1) => fn(page, chunk, undefined, undefined).then((data) => { - $scope.progress = { - current: data.pagination.end, - total: data.pagination.total, - percent: Math.round((data.pagination.end / data.pagination.total) * 100) - }; - if (data.pagination.has_next) { - return fetchRecords(fn, page + 1).then((newData) => data.results.concat(newData)); - } - return data.results; - }); + const fetchRecords = async (fn) => { + pagination = await fn(1, chunk, undefined, undefined).then(data => data.pagination); + + $scope.progress = {current: 0, total: pagination.total, percent:0}; + + page_numbers = [...Array(pagination.num_pages).keys()] + page_promises = page_numbers.map(page => { + return fn(page, chunk, undefined, undefined).then(data => { + num_data = data.pagination.end - data.pagination.start + 1; + $scope.progress.current += num_data; + $scope.progress.percent += Math.round((num_data / data.pagination.total) * 100) + return data.results + }) + }) + + return Promise.all(page_promises).then(pages => [].concat(...pages)) + } + $scope.progress = {}; const loadingModal = $uibModal.open({ diff --git a/seed/static/seed/js/controllers/mapping_controller.js b/seed/static/seed/js/controllers/mapping_controller.js index 41070251df..c32946a82f 100644 --- a/seed/static/seed/js/controllers/mapping_controller.js +++ b/seed/static/seed/js/controllers/mapping_controller.js @@ -619,15 +619,24 @@ angular.module('BE.seed.controller.mapping', []).controller('mapping_controller' /** * empty_units_present: used to disable or enable the 'Map Your Data' button if any units are empty */ - $scope.empty_units_present = () => Boolean(_.find($scope.mappings, (field) => field.suggestion_table_name === 'PropertyState' && field.from_units === null && ($scope.is_area_column(field) || $scope.is_eui_column(field)))); + $scope.empty_units_present = () => { + return $scope.mappings.some((field) => ( + !field.isOmitted && + field.suggestion_table_name === 'PropertyState' && + field.from_units === null && + ($scope.is_area_column(field) || $scope.is_eui_column(field)) + )) + }; /** * empty_fields_present: used to disable or enable the 'show & review * mappings' button. No warning associated as users "aren't done" listing their mapping settings. */ const suggestions_not_provided_yet = () => { - const no_suggestion_value = Boolean(_.find($scope.mappings, { suggestion: undefined })); - const no_suggestion_table_name = Boolean(_.find($scope.mappings, { suggestion_table_name: undefined })); + const non_omitted_mappings = $scope.mappings.filter(m => !m.isOmitted) + const no_suggestion_value = Boolean(_.find(non_omitted_mappings, { suggestion: undefined })); + const no_suggestion_table_name = Boolean(_.find(non_omitted_mappings, { suggestion_table_name: undefined })); + return no_suggestion_value || no_suggestion_table_name; }; diff --git a/seed/static/seed/js/controllers/program_setup_controller.js b/seed/static/seed/js/controllers/program_setup_controller.js index f292b4679d..cc2c130986 100644 --- a/seed/static/seed/js/controllers/program_setup_controller.js +++ b/seed/static/seed/js/controllers/program_setup_controller.js @@ -5,39 +5,49 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup_controller', [ '$scope', '$state', - '$stateParams', - 'compliance_metrics', + '$uibModalInstance', 'compliance_metric_service', - 'filter_groups', 'Notification', + 'spinner_utility', + 'naturalSort', + 'cycles', + 'compliance_metrics', 'organization_payload', - 'cycles_payload', + 'filter_groups', 'property_columns', - 'spinner_utility', - 'x_axis_columns', + 'id', // eslint-disable-next-line func-names function ( $scope, $state, - $stateParams, - compliance_metrics, + $uibModalInstance, compliance_metric_service, - filter_groups, Notification, + spinner_utility, + naturalSort, + cycles, + compliance_metrics, organization_payload, - cycles_payload, + filter_groups, property_columns, - spinner_utility, - x_axis_columns + id ) { - spinner_utility.show(); + // spinner_utility.show(); $scope.state = $state.current; - $scope.id = $stateParams.id; - $scope.org = organization_payload.organization; - $scope.cycles = cycles_payload.cycles; + $scope.org = organization_payload; + $scope.cycles = cycles; + $scope.id = id; + $scope.filter_groups = filter_groups; // order cycles by start date - $scope.cycles = _.orderBy($scope.cycles, ['start'], - ['asc']); + $scope.cycles = _.orderBy($scope.cycles, ['start'], ['asc']); + $scope.filter_groups = filter_groups; + $scope.valid_column_data_types = ['number', 'float', 'integer', 'ghg', 'ghg_intensity', 'area', 'eui', 'boolean']; + $scope.valid_x_axis_data_types = ['number', 'string', 'float', 'integer', 'ghg', 'ghg_intensity', 'area', 'eui', 'boolean']; + + $scope.property_columns = _.reject(property_columns, (item) => item.related || !$scope.valid_column_data_types.includes(item.data_type)).sort((a, b) => naturalSort(a.displayName, b.displayName)); + $scope.x_axis_columns = _.reject(property_columns, (item) => item.related || !$scope.valid_x_axis_data_types.includes(item.data_type)).sort((a, b) => naturalSort(a.displayName, b.displayName)); + $scope.x_axis_selection = ''; + $scope.cycle_selection = ''; $scope.compliance_metrics_error = []; $scope.program_settings_not_changed = true; $scope.program_settings_changed = () => { @@ -45,12 +55,35 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup }; $scope.compliance_metrics = compliance_metrics; $scope.has_compliance_metrics = $scope.compliance_metrics.length > 0; + $scope.selected_compliance_metric = null; - if ($scope.id) { - $scope.selected_compliance_metric = $scope.compliance_metrics.find((item) => item.id === $scope.id); - } - $scope.property_columns = property_columns; - $scope.x_axis_columns = x_axis_columns; + // init_selected_compliance_metric (handle case where there are none) + $scope.init_selected_metric = (id) => { + $scope.has_compliance_metrics = $scope.compliance_metrics.length > 0; + $scope.selected_compliance_metric = null; + $scope.available_cycles = []; + $scope.available_x_axis_columns = []; + $scope.compliance_metrics_error = []; + $scope.program_settings_not_changed = true; + $scope.x_axis_selection = ''; + $scope.cycle_selection = ''; + $scope.available_x_axis_columns = () => []; + $scope.available_cycles = () => []; + + if (id === null) { + if ($scope.has_compliance_metrics) { + // this is after a delete. choose the first metric? + id = $scope.compliance_metrics[0].id; + } + } + if (id != null) { + $scope.selected_compliance_metric = $scope.compliance_metrics.find((item) => item.id === id); + $scope.available_x_axis_columns = () => $scope.x_axis_columns.filter(({ id }) => !$scope.selected_compliance_metric?.x_axis_columns.includes(id)); + $scope.available_cycles = () => $scope.cycles.filter(({ id }) => !$scope.selected_compliance_metric?.cycles.includes(id)); + } + }; + + $scope.init_selected_metric($scope.id); $scope.get_column_display = (id) => { const record = _.find($scope.property_columns, { id }); @@ -60,7 +93,6 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup }; // cycles - $scope.cycle_selection = ''; $scope.get_cycle_display = (id) => { const record = _.find($scope.cycles, { id }); if (record) { @@ -68,8 +100,6 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup } }; - $scope.available_cycles = () => $scope.cycles.filter(({ id }) => !$scope.selected_compliance_metric?.cycles.includes(id)); - $scope.select_cycle = () => { $scope.program_settings_changed(); const selection = $scope.cycle_selection; @@ -79,7 +109,6 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup } $scope.selected_compliance_metric.cycles.push(selection); $scope.order_selected_cycles(); - }; $scope.order_selected_cycles = () => { @@ -100,10 +129,6 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup } }; - $scope.available_x_axis_columns = () => $scope.x_axis_columns.filter(({ id }) => !$scope.selected_compliance_metric?.x_axis_columns.includes(id)); - - $scope.x_axis_selection = ''; - $scope.select_x_axis = () => { $scope.program_settings_changed(); const selection = $scope.x_axis_selection; @@ -120,7 +145,6 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup }; // Filter Groups - $scope.filter_groups = filter_groups; $scope.get_filter_group_display = (id) => { const record = _.find($scope.filter_groups, { id }); if (record) { @@ -128,8 +152,19 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup } }; + $scope.set_program = (id) => { + // check to ensure there are no unsaved changes + if ($scope.program_settings_not_changed) { + // switch it out / re-init + $scope.init_selected_metric(id); + } else { + // warn user to save first + Notification.warning({ message: 'You have unsaved changes to the current program. Save your changes first before selecting a different program to update.', delay: 5000 }); + } + }; + /** - * saves the updates settings + * saves the updated settings */ $scope.save_settings = () => { spinner_utility.show(); @@ -184,22 +219,23 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup $scope.compliance_metrics_error.push(`${key}: ${error}`); } } else { + // success. the ID would already be saved so this block seems unnecesary if (!$scope.selected_compliance_metric.id) { - window.location = `#/accounts/${$scope.org.id}/program_setup/${data.id}`; + $scope.selected_compliance_metric.id = data.id; } - - // replace data into compliance metric? needed? + // replace data into compliance metric const index = _.findIndex($scope.compliance_metrics, ['id', data.id]); - $scope.compliance_metrics[index] = data; - + if (index >= 0) { + $scope.compliance_metrics[index] = data; + } else { + $scope.compliance_metrics.push(data); + } $scope.selected_compliance_metric = data; - - window.location = `#/accounts/${$scope.org.id}/program_setup`; } }); // display messages - Notification.primary({ message: 'Click here to view your Program Overview', delay: 5000 }); + // Notification.primary({ message: 'Click here to view your Program Overview', delay: 5000 }); Notification.success({ message: 'Program Setup Saved!', delay: 5000 }); $scope.program_settings_not_changed = true; @@ -207,7 +243,7 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup }; $scope.click_new_compliance_metric = () => { - spinner_utility.show(); + //spinner_utility.show(); // create a new metric using api and then assign it to selected_compliance_metric that // way it will have an id @@ -224,16 +260,14 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup x_axis_columns: [] }; compliance_metric_service.new_compliance_metric(template_compliance_metric, $scope.org.id).then((data) => { - $scope.selected_compliance_metric = data; - window.location = `#/accounts/${$scope.org.id}/program_setup/${data.id}`; + $scope.compliance_metrics.push(data); + $scope.init_selected_metric(data.id); }); - $scope.program_settings_not_changed = true; - - spinner_utility.hide(); - }; + //spinner_utility.hide(); + } $scope.click_delete = (compliance_metric = null) => { - spinner_utility.show(); + // spinner_utility.show(); if (!compliance_metric) { compliance_metric = $scope.selected_compliance_metric; } @@ -243,12 +277,21 @@ angular.module('BE.seed.controller.program_setup', []).controller('program_setup if (data.status === 'success') { $scope.compliance_metrics = $scope.compliance_metrics.filter((compliance_metric) => compliance_metric.id !== delete_id); if ($scope.selected_compliance_metric.id === delete_id) { - window.location = `#/accounts/${$scope.org.id}/program_setup`; + // notification + Notification.success({ message: 'Compliance metric deleted successfully!', delay: 5000 }); + // reset selection + $scope.selected_compliance_metric = {}; + $scope.init_selected_metric(null); } } }); } - spinner_utility.hide(); + // spinner_utility.hide(); + }; + + $scope.close = () => { + // close and return selected compliance metric + $uibModalInstance.close($scope.selected_compliance_metric); }; } ]); diff --git a/seed/static/seed/js/seed.js b/seed/static/seed/js/seed.js index dd6a09674a..c3ef004670 100644 --- a/seed/static/seed/js/seed.js +++ b/seed/static/seed/js/seed.js @@ -2705,10 +2705,26 @@ SEED_app.config([ 'cycle_service', (cycle_service) => cycle_service.get_cycles() ], + property_columns: [ + 'inventory_service', + 'user_service', + (inventory_service, user_service) => { + const organization_id = user_service.get_organization().id; + return inventory_service.get_property_columns_for_org(organization_id); + } + ], organization_payload: [ 'user_service', 'organization_service', (user_service, organization_service) => organization_service.get_organization(user_service.get_organization().id) + ], + filter_groups: [ + '$stateParams', + 'filter_groups_service', + ($stateParams, filter_groups_service) => { + const inventory_type = 'Property'; // just properties for now + return filter_groups_service.get_filter_groups(inventory_type, $stateParams.organization_id); + } ] } }) @@ -2734,6 +2750,26 @@ SEED_app.config([ 'user_service', 'organization_service', (user_service, organization_service) => organization_service.get_organization(user_service.get_organization().id) + ], + filter_groups: [ + '$stateParams', + 'filter_groups_service', + ($stateParams, filter_groups_service) => { + const inventory_type = 'Property'; // just properties for now + return filter_groups_service.get_filter_groups(inventory_type, $stateParams.organization_id); + } + ], + property_columns: [ + 'inventory_service', + 'user_service', + (inventory_service, user_service) => { + const organization_id = user_service.get_organization().id; + return inventory_service.get_property_columns_for_org(organization_id); + } + ], + cycles: [ + 'cycle_service', + (cycle_service) => cycle_service.get_cycles() ] } }) diff --git a/seed/static/seed/partials/accounts.html b/seed/static/seed/partials/accounts.html index 1f5cd37499..1464ccbeb0 100644 --- a/seed/static/seed/partials/accounts.html +++ b/seed/static/seed/partials/accounts.html @@ -42,7 +42,6 @@

{$:: 'Organizations I Manage' | translate {$:: 'Email Templates' | translate $} {$:: 'Labels' | translate $} {$:: 'Members' | translate $} - {$:: 'Program Setup' | translate $} Settings {$:: 'Sharing' | translate $} {$:: 'Sub-Organizations' | translate $} diff --git a/seed/static/seed/partials/accounts_nav.html b/seed/static/seed/partials/accounts_nav.html index 7136f5f18c..5e2b619941 100644 --- a/seed/static/seed/partials/accounts_nav.html +++ b/seed/static/seed/partials/accounts_nav.html @@ -30,7 +30,6 @@ Email Templates Labels Members -Program Setup Settings Sharing Sub-Organizations diff --git a/seed/static/seed/partials/insights_program.html b/seed/static/seed/partials/insights_program.html index 4dc7f44de0..78516d3815 100644 --- a/seed/static/seed/partials/insights_program.html +++ b/seed/static/seed/partials/insights_program.html @@ -19,7 +19,10 @@

{$:: 'Program Overview' | translate $}

-

If you are not seeing a chart on this page, visit the Program page to configure your program's metrics.

+

Configure your program's metrics:

+
@@ -31,7 +34,11 @@

{$:: 'Program Overview' | translate $}

-

Need to configure your Program? Program Configuration page.

+

Need to configure your Program? + +

diff --git a/seed/static/seed/partials/insights_property.html b/seed/static/seed/partials/insights_property.html index 809934507c..acd10941bc 100644 --- a/seed/static/seed/partials/insights_property.html +++ b/seed/static/seed/partials/insights_property.html @@ -15,12 +15,19 @@
-

If you are not seeing a chart on this page, visit the Program page to configure your program's metrics.

+

Configure your program's metrics:

+
-

CONFIGURE_PROGRAM Program Configuration page.

+

Need to configure your Program? + +

@@ -28,10 +35,14 @@
-
Chart Options
-
-

INSIGHTS_HELP_TEXT

-
+
+ Chart Options + + + +
+
+

INSIGHTS_HELP_TEXT

diff --git a/seed/static/seed/partials/program_setup.html b/seed/static/seed/partials/program_setup.html index e7004d5261..58667e3344 100644 --- a/seed/static/seed/partials/program_setup.html +++ b/seed/static/seed/partials/program_setup.html @@ -1,30 +1,16 @@ -
- -
- -
-
-
- -
-
-
-
-

{$:: 'Program Setup' | translate $}

-
-
- - + -
-
-

Visualization Settings

-
-
- Select at least one field which will serve as the x-axis for visualizations on the - property insights page. Multiple fields can be selected. -
-
    -
  • X-Axis Field Options
  • -
  • - {$:: get_x_axis_display(item) $} - -
  • -
  • - -
  • -
-
@@ -192,6 +157,27 @@

{$:: 'Program Setup' | translate $}

+
+
+

Visualization Settings

+
+
+ Select at least one field which will serve as the x-axis for visualizations on the + property insights page. Multiple fields can be selected. +
+
    +
  • X-Axis Field Options
  • +
  • + {$:: get_x_axis_display(item) $} + +
  • +
  • + +
  • +
+
  • {$ error $}
  • @@ -206,4 +192,8 @@

    {$:: 'Program Setup' | translate $}

+ +
diff --git a/seed/static/seed/partials/sensors_upload_modal.html b/seed/static/seed/partials/sensors_upload_modal.html index 2ab51d6143..9e0460d459 100644 --- a/seed/static/seed/partials/sensors_upload_modal.html +++ b/seed/static/seed/partials/sensors_upload_modal.html @@ -49,7 +49,7 @@ class="btn btn-primary col-sm-6 center-block" sd-uploader organization-id="organization_id" - sourcetype="SensorMetaData" + sourcetype="SensorMetadata" sourceprog="" sourcever="" importrecord="selectedDataset.id" diff --git a/seed/static/seed/scss/style.scss b/seed/static/seed/scss/style.scss index abdbdf2fdf..4b1dbe776e 100755 --- a/seed/static/seed/scss/style.scss +++ b/seed/static/seed/scss/style.scss @@ -3750,7 +3750,7 @@ $pairedCellWidth: 60px; div { position: relative; text-align: right; - top: -2px; + top: -6px; width: 20px; } } diff --git a/seed/tests/test_meter_views.py b/seed/tests/test_meter_views.py index ef67a14d41..c57aec190a 100644 --- a/seed/tests/test_meter_views.py +++ b/seed/tests/test_meter_views.py @@ -545,6 +545,47 @@ def test_bulk_import(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 3) + def test_bulk_import_duplicate_dates(self): + """Bulk Meter Readings with duplicate start_time and end_time pairs will be rejected to avoid sql errors""" + + property_view = self.property_view_factory.get_property_view() + url = reverse('api:v3:property-meters-list', kwargs={'property_pk': property_view.id}) + + payload = { + 'type': 'Electric', + 'source': 'Manual Entry', + 'source_id': '1234567890', + } + + response = self.client.post(url, data=json.dumps(payload), content_type='application/json') + meter_pk = response.json()['id'] + + url = reverse('api:v3:property-meter-readings-list', kwargs={'property_pk': property_view.id, 'meter_pk': meter_pk}) + + # prepare the data in bulk format + reading1 = { + "start_time": "2022-01-05 05:00:00", + "end_time": "2022-01-05 06:00:00", + "reading": 10, + "source_unit": "Wh (Watt-hours)", + # conversion factor is required and is the conversion from the source unit to kBTU (1 Wh = 0.00341 kBtu) + "conversion_factor": 0.00341, + } + reading2 = dict(reading1) + reading2["end_time"] = "2022-01-05 07:00:00" + payload = [reading1, reading2] + + response = self.client.post(url, data=json.dumps(payload), content_type='application/json') + self.assertEqual(response.status_code, 201) + self.assertEqual(response.json()[0]['reading'], 10) + self.assertEqual(response.json()[1]['reading'], 10) + + # Duplicate start and end times will be rejected + payload = [reading1, reading1] + response = self.client.post(url, data=json.dumps(payload), content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()['non_field_errors'], ['Error: Each reading must have a unique combination of start_time end end_time.']) + def test_delete_meter_readings(self): # would be nice nice to make a factory out of the meter / meter reading requests property_view = self.property_view_factory.get_property_view() diff --git a/seed/utils/sensors.py b/seed/utils/sensors.py index eb1273e1fc..0ffa6f4df7 100644 --- a/seed/utils/sensors.py +++ b/seed/utils/sensors.py @@ -50,7 +50,8 @@ def _usages_by_exact_times(self, page, per_page): sensor_readings = SensorReading.objects.filter(sensor__in=self.sensors) if self.showOnlyOccupiedReadings: sensor_readings = sensor_readings.filter(is_occupied=True) - timestamps = sensor_readings.distinct('timestamp').order_by("timestamp").values_list("timestamp", flat=True) + + timestamps = list(sensor_readings.distinct('timestamp').order_by('timestamp').values_list("timestamp", flat=True)) paginator = Paginator(timestamps, per_page) timestamps_in_page = paginator.page(page) @@ -71,19 +72,19 @@ def _usages_by_exact_times(self, page, per_page): time_format = "%Y-%m-%d %H:%M:%S" - for sensor in self.sensors: - field_name = self._build_column_def(sensor, column_defs) + field_name_by_sensor_id = { + sensor.id: self._build_column_def(sensor, column_defs) + for sensor in self.sensors + } - sensor_readings = sensor.sensor_readings.filter(timestamp__range=[earliest_time, latest_time]) - if self.showOnlyOccupiedReadings: - sensor_readings = sensor_readings.filter(is_occupied=True) + sensor_readings = sensor_readings.filter(timestamp__range=[earliest_time, latest_time]) - for sensor_reading in sensor_readings.all(): - timestamp = sensor_reading.timestamp.astimezone(tz=self.tz).strftime(time_format) - times_key = str(timestamp) + for sensor_reading in sensor_readings.all(): + timestamp = sensor_reading.timestamp.astimezone(tz=self.tz).strftime(time_format) + times_key = str(timestamp) - timestamps[times_key]["timestamp"] = timestamp - timestamps[times_key][field_name] = sensor_reading.reading + timestamps[times_key]["timestamp"] = timestamp + timestamps[times_key][field_name_by_sensor_id[sensor_reading.sensor_id]] = sensor_reading.reading return { 'pagination': { diff --git a/seed/views/v3/audit_template.py b/seed/views/v3/audit_template.py index 1dfb398d10..20082ea92c 100644 --- a/seed/views/v3/audit_template.py +++ b/seed/views/v3/audit_template.py @@ -4,6 +4,8 @@ SEED Platform (TM), Copyright (c) Alliance for Sustainable Energy, LLC, and other contributors. See also https://github.com/seed-platform/seed/main/LICENSE.md """ +import json + from django.http import HttpResponse, JsonResponse from drf_yasg.utils import swagger_auto_schema from rest_framework import viewsets @@ -37,7 +39,7 @@ def get_submission(self, request, pk): default_report_format = 'pdf' report_format = request.query_params.get('report_format', default_report_format) - valid_file_formats = ['xml', 'pdf'] + valid_file_formats = ['json', 'xml', 'pdf'] if report_format.lower() not in valid_file_formats: message = f"The report_format specified is invalid. Must be one of: {valid_file_formats}." return JsonResponse({ @@ -49,18 +51,23 @@ def get_submission(self, request, pk): at = AuditTemplate(self.get_organization(self.request)) response, message = at.get_submission(pk, report_format) + # error if response is None: return JsonResponse({ 'success': False, 'message': message }, status=400) + # json + if report_format.lower() == 'json': + return JsonResponse(json.loads(response.content)) + # xml if report_format.lower() == 'xml': return HttpResponse(response.text) - else: - response2 = HttpResponse(response.content) - response2.headers["Content-Type"] = 'application/pdf' - response2.headers["Content-Disposition"] = f'attachment; filename="at_submission_{pk}.pdf"' - return response2 + # pdf + response2 = HttpResponse(response.content) + response2.headers["Content-Type"] = 'application/pdf' + response2.headers["Content-Disposition"] = f'attachment; filename="at_submission_{pk}.pdf"' + return response2 @swagger_auto_schema(manual_parameters=[AutoSchemaHelper.query_org_id_field()]) @has_perm_class('can_view_data') diff --git a/tox.ini b/tox.ini index e8cf581650..25cc8bfbc4 100644 --- a/tox.ini +++ b/tox.ini @@ -44,6 +44,8 @@ changedir=docs deps= -r{toxinidir}/requirements/test.txt commands= + # After we fix doc build links/issues, then add the -W flag + ; make spelling SPHINXOPTS='-W --keep-going' make spelling sphinx-build -b html -d {envtmpdir}/doctrees {toxinidir}/docs/source {envtmpdir}/html whitelist_externals= @@ -76,7 +78,6 @@ passenv= TRAVIS_BUILD_NUMBER MAPQUEST_API_KEY whitelist_externals= - ; coveralls-merge cp npm diff --git a/vendors/package-lock.json b/vendors/package-lock.json index 70c26d79d7..e522dafae2 100644 --- a/vendors/package-lock.json +++ b/vendors/package-lock.json @@ -24,7 +24,7 @@ "angular-translate-interpolation-messageformat": "^2.19.0", "angular-translate-loader-static-files": "^2.19.0", "angular-ui-bootstrap": "^2.5.6", - "angular-ui-grid": "^4.12.2", + "angular-ui-grid": "^4.12.4", "angular-ui-notification": "^0.3.6", "angular-ui-router": "^1.0.30", "angular-ui-router.statehelper": "^1.3.1", @@ -1985,9 +1985,9 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", diff --git a/vendors/package.json b/vendors/package.json index 22c18dc050..1924f0ee7d 100644 --- a/vendors/package.json +++ b/vendors/package.json @@ -20,7 +20,7 @@ "angular-translate-interpolation-messageformat": "^2.19.0", "angular-translate-loader-static-files": "^2.19.0", "angular-ui-bootstrap": "^2.5.6", - "angular-ui-grid": "^4.12.2", + "angular-ui-grid": "^4.12.4", "angular-ui-notification": "^0.3.6", "angular-ui-router": "^1.0.30", "angular-ui-router.statehelper": "^1.3.1",