diff --git a/.gitignore b/.gitignore index acdedceca9..501a16a28b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ src/ /seed/static/vendors/bower_components/fine-uploader/ docs/build/ .project -.pydevproject \ No newline at end of file +.pydevproject +dump.rdb diff --git a/BE/settings/dev.py b/BE/settings/dev.py index 3ceb6b2185..d7eef9a3a3 100644 --- a/BE/settings/dev.py +++ b/BE/settings/dev.py @@ -49,7 +49,7 @@ LOGGING = { 'version': 1, 'disable_existing_loggers': True, - # set up some log message handers to chose from + # set up some log message handlers to choose from 'handlers': { 'sentry': { 'level': 'ERROR', diff --git a/README-osx.md b/README-osx.md index e08f8cb224..3866c5c002 100644 --- a/README-osx.md +++ b/README-osx.md @@ -4,18 +4,27 @@ These instructions are for installing and running SEED on Mac OSX in development ## Prerequisites -These instructions assume you have/use [Macports](https://www.macports.org/). +These instructions assume you have/use [Macports](https://www.macports.org/). The workflow has been testing with homebrew as well, but is not directly supported. You system should have the following dependencies already installed: -Although you _could_ install Python packages globally, the easiest way to install Python packages is with [virtualenv](https://virtualenv.pypa.io/en/latest/) and [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/). Setting these up first will help avoid polluting your base Python installation and make it much easier to switch between different versions of the code. +* git (`port install git` or `brew install git`) +* Mercurial (`port install hg` or `brew install mercurial`) -Once you have these installed, creating and entering a new virtualenv called "``seed``" for SEED development is as easy as: +(Recommended) - mkvirtualenv --python=python2.7 seed - +* [virtualenv](https://virtualenv.pypa.io/en/latest/) and [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/). + * Although you _could_ install Python packages globally, this is the easiest way to install Python packages. Setting these up first will help avoid polluting your base Python installation and make it much easier to switch between different versions of the code. + + pip install virtualenv + pip install virtualenvwrapper + + * Follow instructions on [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to setup your environment. + * Once you have these installed, creating and entering a new virtualenv called "``seed``" for SEED development is as easy as: + + mkvirtualenv --python=python2.7 seed ## Install PostgreSQL 9.4 -Perform the following commands as 'root' +Perform the following commands as 'root' if using port sudo su - root @@ -25,7 +34,12 @@ Install Postgres 9.4 # init db mkdir -p /opt/local/var/db/postgresql94/defaultdb chown postgres:postgres /opt/local/var/db/postgresql94/defaultdb - + + # homebrew + brew install postgres + # follow the post install instructions to add to launchagents or call manually with `postgres -D /usr/local/var/postgres` + # Skip the remaining Postgres instructions + Finish initializing the DB sudo su postgres -c '/opt/local/lib/postgresql94/bin/initdb -D /opt/local/var/db/postgresql94/defaultdb' @@ -44,11 +58,10 @@ Start Postgres Switch to postgres user - sudo su - postgres - PATH=$PATH:/opt/local/lib/postgresql94/bin/ + sudo su - postgres + PATH=$PATH:/opt/local/lib/postgresql94/bin/ -Configure Postgresql. Replace 'seeddb', 'seeduser' with desired db/user. -seedpass +Configure PostgreSQL. Replace 'seeddb', 'seeduser' with desired db/user. By default use password `seedpass` when prompted createdb seeddb createuser -P seeduser @@ -60,9 +73,11 @@ Now exit any root environments, becoming just yourself (even though it's not tha Run these commands as your normal user id. -Change to a virtualenv (using virtualenvwrapper) or do the following as a superuser. A virtualenv is usually better for development. +Change to a virtualenv (using virtualenvwrapper) or do the following as a superuser. A virtualenv is usually better for development. Set the virtualenv to seed. + + workon seed -Make sure PostgreSQL command line scripts are in your PATH +Make sure PostgreSQL command line scripts are in your PATH (if using port) export PATH=$PATH:/opt/local/lib/postgresql94/bin @@ -85,13 +100,11 @@ Install library with `setup.py` First, install [npm](https://www.npmjs.com/) if you haven't already. You can do this by installing from [nodejs.org](http://nodejs.org/), or use Macports: + # port sudo port install npm -### Install libraries - -Then run, from the top-level, a script to install the JS libraries: - - ./bin/install_javascript_dependencies.sh + # homebrew + brew install npm ## Configure Django and its back-end DBs @@ -150,7 +163,7 @@ If you want to do any API testing (and of course you do!), you will need to add an API KEY for this user. You can do this in postgresql directly: - psql94 seeddb seeduser + psql seeddb seeduser seeddb=> update landing_seeduser set api_key='DEADBEEF' where id=1; The 'secret' key DEADBEEF is hard-coded into the test scripts. @@ -159,8 +172,13 @@ The 'secret' key DEADBEEF is hard-coded into the test scripts. You need to manually install Redis for Celery to work. + # port sudo port install redis + # homebrew + brew install redis + # follow the post install instructions to add to launchagents or call manually with `redis-server` + ### Install Javascript dependencies The JS dependencies are installed using node.js package management (npm), with diff --git a/green_button/xml_importer.py b/green_button/xml_importer.py index 9c5909e5aa..9275b1dc08 100644 --- a/green_button/xml_importer.py +++ b/green_button/xml_importer.py @@ -119,7 +119,7 @@ def interval_data(reading_xml_data): :returns: dictionary representing a time series reading with keys 'cost', 'value', 'start_time', and 'duration'. """ - cost = reading_xml_data['cost'] + cost = reading_xml_data.get('cost') value = reading_xml_data['value'] time_period = reading_xml_data['timePeriod'] @@ -155,8 +155,8 @@ def meter_data(raw_meter_meta): # this function currently assumes those types are present and does # not check for any other types - currency = params_data['currency'] - power_of_ten_multiplier = params_data['powerOfTenMultiplier'] + currency = params_data.get('currency') + power_of_ten_multiplier = params_data.get('powerOfTenMultiplier') uom = params_data['uom'] result = { diff --git a/seed/decorators.py b/seed/decorators.py index d8cd035071..d9183a429b 100644 --- a/seed/decorators.py +++ b/seed/decorators.py @@ -22,6 +22,7 @@ def _get_lock_key(func_name, import_file_pk): def get_prog_key(func_name, import_file_pk): + """Return the progress key for the cache""" return _get_cache_key( PROGRESS_CACHE_PREFIX.format(func_name), import_file_pk ) @@ -29,14 +30,14 @@ def get_prog_key(func_name, import_file_pk): def increment_cache(key, increment): """Increment cache by value increment, never exceed 100.""" - value = cache.get(key) or 0.0 - value = float(value) + value = cache.get(key) or {'status': 'parsing', 'progress': 0.0} + value = float(value['progress']) if value + increment >= 100.0: value = 100.0 else: value += increment - cache.set(key, value) + cache.set(key, {'status': 'parsing', 'progress': value}) def lock_and_track(fn, *args, **kwargs): diff --git a/seed/models.py b/seed/models.py index 49a9638b1a..e34bb8cb5a 100644 --- a/seed/models.py +++ b/seed/models.py @@ -152,15 +152,16 @@ def get_ancestors(building): source_type { 2: ASSESSED_BS, 3: PORTFOLIO_BS, - 4: COMPOSITE_BS + 4: COMPOSITE_BS, + 6: GREEN_BUTTON_BS } :param building: BuildingSnapshot inst. :returns: list of BuildingSnapshot inst., ancestors of building """ ancestors = [] - parents = building.parents.filter(source_type__in=[2, 3, 4]) - ancestors.extend(parents.filter(source_type__in=[2, 3])) + parents = building.parents.filter(source_type__in=[2, 3, 4, 6]) + ancestors.extend(parents.filter(source_type__in=[2, 3, 6])) for p in parents: ancestors.extend(get_ancestors(p)) return ancestors diff --git a/seed/search.py b/seed/search.py index edd78eee5e..b82f8082d9 100644 --- a/seed/search.py +++ b/seed/search.py @@ -8,6 +8,7 @@ # python import operator import json +import re # django from django.db.models import Q @@ -184,49 +185,90 @@ def is_column(k, columns): sanitized = strip_suffixes(k, ['__lt', '__gt', '__lte', '__gte']) if sanitized in columns: return True - else: - return False + return False + + def is_string_query(q): + return isinstance(q, basestring) + + def is_exact_match(q): + if is_string_query(q): + return re.match(r"""^(["'])(.+)\1$""", q) + return False + + def is_empty_match(q): + if is_string_query(q): + return re.match(r"""^(["'])\1$""", q) + return False - query_dict = {} + # Build query as Q objects so we can AND and OR. + query_filters = Q() for k, v in other_params.iteritems(): in_columns = is_column(k, db_columns) - if in_columns and k != 'q' and v is not None and v != '': - if ('__lt' in k or + if in_columns and k != 'q' and v: + + # Is this query surrounded by matching quotes? + exact_match = is_exact_match(v) + empty_match = is_empty_match(v) + + if exact_match: + query_filters &= Q(**{"%s__exact" % k: exact_match.group(2)}) + elif empty_match: + query_filters &= Q(**{"%s__exact" % k: ''}) | Q(**{"%s__isnull" % k: True}) + elif ('__lt' in k or '__lte' in k or '__gt' in k or '__gte' in k or '__isnull' in k or k == 'import_file_id' or k == 'source_type'): - query_dict["%s" % k] = v + query_filters &= Q(**{"%s" % k: v}) else: - query_dict["%s__icontains" % k] = v + query_filters &= Q(**{"%s__icontains" % k: v}) - queryset = queryset.filter(**query_dict) + queryset = queryset.filter(query_filters) # handle extra_data with json_query for k, v in other_params.iteritems(): - if (not is_column(k, db_columns)) and k != 'q' and v != '': - if k.endswith(('__gt', '__gte')): + if (not is_column(k, db_columns)) and k != 'q' and v: + + # Is this query surrounded by matching quotes? + exact_match = is_exact_match(v) + empty_match = is_empty_match(v) + + + # If querying for empty matches, do a hack-y 'contains' query on + # the json field to check if the field exists. We're checking + # existence because empty mapped fields are not saved in the + # extra_data json field if they contain no data. + # + # When we bump to Django 1.7, we can switch to the newer + # django-pgjson package, and use the new "HAS" operator syntax. + # - nicholasserra + if empty_match: + queryset = queryset.exclude(extra_data__contains='"%s":' % k) + continue + + conditions = { + 'value': v + } + + if exact_match: + conditions['value'] = exact_match.group(2) + conditions['key_cast'] = 'text' + elif k.endswith(('__gt', '__gte')): k = strip_suffixes(k, ['__gt', '__gte']) - cond = '>' - key_cast = 'float' + conditions['cond'] = '>' + conditions['key_cast'] = 'float' elif k.endswith(('__lt', '__lte')): k = strip_suffixes(k, ['__lt', '__lte']) - cond = '<' - key_cast = 'float' + conditions['cond'] = '<' + conditions['key_cast'] = 'float' else: - cond = 'LIKE' - key_cast = 'text' - v = "%{0}%".format(v) - case_insensitive = key_cast == 'text' - - queryset = queryset.json_query( - k, - cond=cond, - key_cast=key_cast, - value=v, - case_insensitive=case_insensitive, - ) + conditions['cond'] = 'LIKE' + conditions['key_cast'] = 'text' + conditions['value'] = "%{0}%".format(v) + conditions['case_insensitive'] = True + + queryset = queryset.json_query(k, **conditions) return queryset diff --git a/seed/static/seed/css/mapping.css b/seed/static/seed/css/mapping.css index c586218d56..7c41a9634f 100644 --- a/seed/static/seed/css/mapping.css +++ b/seed/static/seed/css/mapping.css @@ -7,9 +7,9 @@ margin: 10px; } .fa-arrows { - padding-right: 7px; - color: #444; - } + padding-right: 7px; + color: #444; +} .tree-handle { padding: 10px; background: #428bca; diff --git a/seed/static/seed/js/controllers/admin_controller.js b/seed/static/seed/js/controllers/admin_controller.js index 0bec88ec4c..321ee8fec8 100644 --- a/seed/static/seed/js/controllers/admin_controller.js +++ b/seed/static/seed/js/controllers/admin_controller.js @@ -162,6 +162,8 @@ angular.module('BE.seed.controller.admin', []) function(data){ //success fn org.remove_message = "success"; get_organizations(); + }, function(data){ //failure fn + // Do nothing }, org // progress bar obj ); diff --git a/seed/static/seed/js/controllers/building_detail_controller.js b/seed/static/seed/js/controllers/building_detail_controller.js index bbc78c6fbd..c67c93ffef 100644 --- a/seed/static/seed/js/controllers/building_detail_controller.js +++ b/seed/static/seed/js/controllers/building_detail_controller.js @@ -32,6 +32,15 @@ angular.module('BE.seed.controller.building_detail', []) $scope.building_copy = {}; $scope.data_columns = []; $scope.audit_logs = audit_payload.audit_logs; + $scope.green_button_filenames = []; + + // gather green button filenames + building_payload.imported_buildings.forEach(function(e) { + if (e.source_type == 6) { // GREEN_BUTTON_BS + $scope.green_button_filenames.push(e.import_file_name); + } + }); + // set the tab $scope.section = $location.hash(); /** diff --git a/seed/static/seed/js/controllers/building_list_controller.js b/seed/static/seed/js/controllers/building_list_controller.js index f9e2ef4e86..e30e33ecec 100644 --- a/seed/static/seed/js/controllers/building_list_controller.js +++ b/seed/static/seed/js/controllers/building_list_controller.js @@ -66,6 +66,7 @@ angular.module('BE.seed.controller.building_list', []) */ var get_columns = function() { $scope.assessor_fields = all_columns.fields; + $scope.search.init_storage(); $scope.columns = $scope.search.generate_columns( all_columns.fields, default_columns.columns, diff --git a/seed/static/seed/js/controllers/data_upload_modal_ctrl.js b/seed/static/seed/js/controllers/data_upload_modal_ctrl.js index d9bac3ed74..250676c9e4 100644 --- a/seed/static/seed/js/controllers/data_upload_modal_ctrl.js +++ b/seed/static/seed/js/controllers/data_upload_modal_ctrl.js @@ -18,11 +18,14 @@ * ng-switch-when="8" == Add files to your Data Set. * ng-switch-when="9" == Add files to {$ dataset.name $} * ng-switch-when="10" == No matches found + * ng-switch-when="11" == Confirm Save Mappings? + * ng-switch-when="12" == Error Processing Data */ angular.module('BE.seed.controller.data_upload_modal', []) .controller('data_upload_modal_ctrl', [ '$scope', '$modalInstance', + '$log', 'step', 'dataset', '$timeout', @@ -34,6 +37,7 @@ angular.module('BE.seed.controller.data_upload_modal', []) function ( $scope, $modalInstance, + $log, step, dataset, $timeout, @@ -161,7 +165,6 @@ angular.module('BE.seed.controller.data_upload_modal', []) } if (event_message === "upload_complete") { var current_step = $scope.step.number; - var data_is_green_button = $scope.uploader.status_message = "upload complete"; $scope.dataset.import_file_id = file.file_id; @@ -216,6 +219,8 @@ angular.module('BE.seed.controller.data_upload_modal', []) mapping_service.start_mapping(file_id).then(function (dataa){ monitor_mapping(dataa.progress_key, file_id); }); + }, function(data) { + // Do nothing }, $scope.uploader); }; @@ -232,6 +237,8 @@ angular.module('BE.seed.controller.data_upload_modal', []) matching_service.start_system_matching(file_id).then(function (dataa){ monitor_matching(dataa.progress_key, file_id); }); + }, function(data) { + // Do nothing }, $scope.uploader); }; @@ -248,6 +255,8 @@ angular.module('BE.seed.controller.data_upload_modal', []) $scope.uploader.in_progress = false; $scope.uploader.progress = 1; $scope.step.number = 5; + }, function(data) { + // Do nothing }, $scope.uploader); }; @@ -271,6 +280,11 @@ angular.module('BE.seed.controller.data_upload_modal', []) $scope.step.number = 3; } + }, function(data){ + $log.error(data.message); + if (data.hasOwnProperty('stacktrace')) $log.error(data.stacktrace); + $scope.step_12_error_message = data.message; + $scope.step.number = 12; }, $scope.uploader); }); }; @@ -311,6 +325,8 @@ angular.module('BE.seed.controller.data_upload_modal', []) $scope.step.number = 10; } }); + }, function(data) { + // Do nothing }, $scope.uploader ); diff --git a/seed/static/seed/js/controllers/dataset_detail_controller.js b/seed/static/seed/js/controllers/dataset_detail_controller.js index 0fbee7d83d..3b339e1743 100644 --- a/seed/static/seed/js/controllers/dataset_detail_controller.js +++ b/seed/static/seed/js/controllers/dataset_detail_controller.js @@ -12,7 +12,7 @@ angular.module('BE.seed.controller.dataset_detail', []) function ($scope, dataset_payload, $log, dataset_service, $modal, urls) { $scope.dataset = dataset_payload.dataset; - $log.info({dataset_payload: dataset_payload}); + $log.info('dataset_payload:', dataset_payload); $scope.confirm_delete = function (file) { var yes = confirm("Are you sure you want to PERMANENTLY delete '" + file.name + "'?"); diff --git a/seed/static/seed/js/controllers/mapping_controller.js b/seed/static/seed/js/controllers/mapping_controller.js index 81decdfe50..fa0b273dec 100644 --- a/seed/static/seed/js/controllers/mapping_controller.js +++ b/seed/static/seed/js/controllers/mapping_controller.js @@ -480,6 +480,8 @@ angular.module('BE.seed.controller.mapping', []) 1.0, // progress multiplier function(data){ //success fn $scope.get_mapped_buildings(); + }, function(data) { //failure fn + // Do nothing }, $scope.import_file // progress bar obj ); diff --git a/seed/static/seed/js/controllers/matching_controller.js b/seed/static/seed/js/controllers/matching_controller.js index c28b56244b..8700cc7dce 100644 --- a/seed/static/seed/js/controllers/matching_controller.js +++ b/seed/static/seed/js/controllers/matching_controller.js @@ -130,6 +130,29 @@ angular.module('BE.seed.controller.matching', []) $scope.next_page_disabled = $scope.current_page === $scope.num_pages; }; + + /** + * first_page: triggered when the `first` paging button is clicked, it + * sets the results to the first page and shows that page + */ + $scope.pagination.first_page = function() { + $scope.current_page = 1; + $scope.filter_search(); + }; + + /** + * last_page: triggered when the `last` paging button is clicked, it + * sets the results to the last page and shows that page + */ + $scope.pagination.last_page = function() { + $scope.current_page = $scope.num_pages; + $scope.filter_search(); + }; + + /** + * next_page: triggered when the `next` paging button is clicked, it + * increments the page of the results, and fetches that page + */ $scope.pagination.next_page = function() { $scope.current_page += 1; if ($scope.current_page > $scope.num_pages) { @@ -137,6 +160,11 @@ angular.module('BE.seed.controller.matching', []) } $scope.filter_search(); }; + + /** + * prev_page: triggered when the `previous` paging button is clicked, it + * decrements the page of the results, and fetches that page + */ $scope.pagination.prev_page = function() { $scope.current_page -= 1; if ($scope.current_page < 1) { diff --git a/seed/static/seed/js/controllers/menu_controller.js b/seed/static/seed/js/controllers/menu_controller.js index 850776a509..4764984966 100644 --- a/seed/static/seed/js/controllers/menu_controller.js +++ b/seed/static/seed/js/controllers/menu_controller.js @@ -50,16 +50,22 @@ angular.module('BE.seed.controller.menu', []) $scope.menu.create_project_error = false; $scope.menu.create_project_error_message = ""; $scope.saving_indicator = false; + $scope.menu.loading = false; $scope.menu.route_load_error = false; $scope.menu.user = {}; $scope.$on("$routeChangeError", function(event, current, previous, rejection) { + $scope.menu.loading = false; $scope.menu.route_load_error = true; if (rejection === "not authorized" || rejection === "Your page could not be located!") { $scope.menu.error_message = rejection; } }); + $scope.$on("$routeChangeStart", function($event, next, current) { + $scope.menu.loading = next.controller === "mapping_controller"; + }); $scope.$on("$routeChangeSuccess", function() { + $scope.menu.loading = false; $scope.menu.route_load_error = false; }); $scope.$on('app_error', function(event, data){ diff --git a/seed/static/seed/js/directives/beUploader.js b/seed/static/seed/js/directives/beUploader.js index 458669bf29..b3d99fda97 100644 --- a/seed/static/seed/js/directives/beUploader.js +++ b/seed/static/seed/js/directives/beUploader.js @@ -11,7 +11,7 @@ * `message` - string - options: "upload_submitted", * "upload_in_progress", "upload_complete", and "invalid_extension" * `filename` - string name of the file uploaded - * `progress` - JS object with keys `loaded` and `total` wehere + * `progress` - JS object with keys `loaded` and `total` where * `loaded` / `total` * 100.0 is the percent uploaded * importrecord - int or string - id of import record or dataset * @@ -111,7 +111,7 @@ var makeS3Uploader = function(scope, element, attrs, filename) { * onComplete: overloaded callback that calls the callback defined * in the element attribute unless the upload failed, which will * fire a window alert. Passes as arguments to the callback - * a message indicating upload has copmleted, "upload_complete", and + * a message indicating upload has completed, "upload_complete", and * the filename. */ onComplete: function(id, fileName, responseJSON) { @@ -242,7 +242,7 @@ var makeFileSystemUploader = function(scope, element, attrs, filename) { * onComplete: overloaded callback that calls the callback defined * in the element attribute unless the upload failed, which will * fire a window alert. Passes as arguments to the callback - * a message indicating upload has copmleted, "upload_complete", and + * a message indicating upload has completed, "upload_complete", and * the filename. */ onComplete: function(id, fileName, responseJSON) { diff --git a/seed/static/seed/js/filters/stripImportPrefix.js b/seed/static/seed/js/filters/stripImportPrefix.js index 32dbb2a52d..4f824a5b4d 100644 --- a/seed/static/seed/js/filters/stripImportPrefix.js +++ b/seed/static/seed/js/filters/stripImportPrefix.js @@ -10,11 +10,11 @@ angular.module('stripImportPrefix', []).filter('stripImportPrefix', [ function($filter) { /** ids are sometime prefixed by the Import Record id. * e.g. import 28 would prefix all assessor data ids with 'IMP28-' and - * stripImportPrefix would stip out the 'IMP28-'s from the html and only + * stripImportPrefix would strip out the 'IMP28-'s from the html and only * display the ids. * * Usage: building.id = "IMP12-007" - * HTML: {{ buidling.id | stripImportPrefix }} + * HTML: {{ building.id | stripImportPrefix }} * compiles to: 007 * JS : stripImportPrefix(building.id) * returns: "007" diff --git a/seed/static/seed/js/seed.js b/seed/static/seed/js/seed.js index d8399b288a..27e0c37b79 100644 --- a/seed/static/seed/js/seed.js +++ b/seed/static/seed/js/seed.js @@ -256,8 +256,16 @@ SEED_app.config(['$routeProvider', function ($routeProvider) { 'search_payload': ['building_services', '$route', function(building_services, $route){ var params = $route.current.params; var q = params.q || ""; + + // Check session storage for order and sort values. + var orderBy = (typeof(Storage) !== "undefined" && sessionStorage.getItem('seedBuildingOrderBy') !== null) ? + sessionStorage.getItem('seedBuildingOrderBy') : ""; + + var sortReverse = (typeof(Storage) !== "undefined" && sessionStorage.getItem('seedBuildingSortReverse') !== null) ? + JSON.parse(sessionStorage.getItem('seedBuildingSortReverse')) : false; + // params: (query, number_per_page, page_number, order_by, sort_reverse, other_params, project_id) - return building_services.search_buildings(q, 10, 1, "", false, params, null); + return building_services.search_buildings(q, 10, 1, orderBy, sortReverse, params, null); }], 'default_columns': ['user_service', function(user_service){ return user_service.get_default_columns(); diff --git a/seed/static/seed/js/services/search_service.js b/seed/static/seed/js/services/search_service.js index 8f1c56e15f..6a78b1faae 100644 --- a/seed/static/seed/js/services/search_service.js +++ b/seed/static/seed/js/services/search_service.js @@ -78,6 +78,18 @@ angular.module('BE.seed.service.search', []) * functions */ + search_service.init_storage = function () { + // Check session storage for order and sort values. + if (typeof(Storage) !== "undefined" && sessionStorage.getItem('seedBuildingOrderBy') !== null) { + saas.order_by = sessionStorage.getItem('seedBuildingOrderBy'); + saas.sort_column = sessionStorage.getItem('seedBuildingOrderBy'); + } + + if (typeof(Storage) !== "undefined" && sessionStorage.getItem('seedBuildingSortReverse') !== null) { + saas.sort_reverse = JSON.parse(sessionStorage.getItem('seedBuildingSortReverse')); + } + }; + /** * sanitize_params: removes filter params with null or undefined values */ @@ -194,6 +206,24 @@ angular.module('BE.seed.service.search', []) this.showing.start = ((this.current_page - 1)*this.number_per_page) + 1; }; + /** + * first_page: triggered when the `first` paging button is clicked, it + * sets the page to the first in the results, and fetches that page + */ + search_service.first_page = function() { + this.current_page = 1; + this.search_buildings(); + }; + + /** + * last_page: triggered when the `last` paging button is clicked, it + * sets the page to the last in the results, and fetches that page + */ + search_service.last_page = function() { + this.current_page = this.num_pages; + this.search_buildings(); + }; + /** * next_page: triggered when the `next` paging button is clicked, it * increments the page of the results, and fetches that page @@ -306,6 +336,12 @@ angular.module('BE.seed.service.search', []) saas.sort_column = this.sort_column; } } + + if (typeof(Storage) !== "undefined") { + sessionStorage.setItem('seedBuildingOrderBy', saas.sort_column); + sessionStorage.setItem('seedBuildingSortReverse', saas.sort_reverse); + } + saas.order_by = this.sort_column; saas.current_page = 1; saas.search_buildings(); diff --git a/seed/static/seed/js/services/uploader_service.js b/seed/static/seed/js/services/uploader_service.js index a08fa2bad3..a58a35c424 100644 --- a/seed/static/seed/js/services/uploader_service.js +++ b/seed/static/seed/js/services/uploader_service.js @@ -115,28 +115,29 @@ angular.module('BE.seed.service.uploader', []).factory('uploader_service', [ * @param {number} offset: where to start the progress bar * @param {number} multiplier: multiplier for progress val * @param {fn} success_fn: function to call when progress is done + * @param {fn} failure_fn: function to call when progress is done and the result was not success * @param {obj} progress_bar_obj: progress bar object, attr 'progress' * is set with the progress */ - uploader_factory.check_progress_loop = function(progress_key, offset, multiplier, success_fn, progress_bar_obj, debug) { + uploader_factory.check_progress_loop = function(progress_key, offset, multiplier, success_fn, failure_fn, progress_bar_obj, debug) { if (typeof debug === 'undefined') { - debug = false; + debug = false; } uploader_factory.check_progress(progress_key).then(function (data){ - if (debug) { - console.log({progress: data.progress}); - } - var stop = $timeout(function(){ - progress_bar_obj.progress = (data.progress * multiplier) + offset; - if (data.progress < 100) { - uploader_factory.check_progress_loop(progress_key, offset, multiplier, success_fn, progress_bar_obj, debug); - } else { - success_fn(data); - } - }, 750); - }, function (data, status) { - // reject promise - console.log(data, status); + if (debug) { + console.log({progress: data.progress}); + } + var stop = $timeout(function(){ + progress_bar_obj.progress = (data.progress * multiplier) + offset; + if (data.progress < 100) { + uploader_factory.check_progress_loop(progress_key, offset, multiplier, success_fn, failure_fn, progress_bar_obj, debug); + } else { + success_fn(data); + } + }, 750); + }, function (data) { + // reject promise + failure_fn(data); }); }; diff --git a/seed/static/seed/partials/about.html b/seed/static/seed/partials/about.html index 8687301246..7048270c77 100644 --- a/seed/static/seed/partials/about.html +++ b/seed/static/seed/partials/about.html @@ -24,8 +24,8 @@
Funding from: U.S. Department of Energy
Software developer: Building Energy
-1.1.4
+1.1.5
diff --git a/seed/static/seed/partials/accounts.html b/seed/static/seed/partials/accounts.html index 0e37c1304e..ed82d56a97 100644 --- a/seed/static/seed/partials/accounts.html +++ b/seed/static/seed/partials/accounts.html @@ -12,29 +12,29 @@Organization | |
---|---|
{$ org.name $} | -+ + |
{$ org.name $} | +Settings SharingSub-Organizations Members Remove | -
Sub-Organizations | Create new sub-organization |
{$ sub_org.name $} | -Members Remove | -
{$ sub_org.name $} | +Members Remove | +
Organization Name | Number of Buildings | Your Role | Organization Owner(s) | |||
---|---|---|---|---|---|---|
+ | {$ org.name $} {$ org.name $} | {$ org.num_buildings $} | -{$ org.user_role || "-" $} | -+ | {$ org.user_role || "-" $} | +
{$ owner.first_name $} {$ owner.last_name $} |
-
Field | -Master | -- {$ i.import_file_name $} - - {$ i.import_file_name $} - - | -
---|---|---|
{$ f.title $} | -
- {$ building[f.sort_column] $}
- {$ building[f.sort_column] | date:'shortDate' $}
- {$ building[f.sort_column] | number:f.num_digits $}
-
-
- {$ building.extra_data[f.sort_column] | number:0 $}
-
-
-
-
-
-
- |
-
- - {$ i[f.sort_column] $} - {$ i[f.sort_column] | date:'shortDate' $} - {$ i[f.sort_column] | number:f.num_digits $} - - {$ i.extra_data[f.sort_column] | number:f.num_digits $} - - {$ i[f.sort_column] $} - {$ i[f.sort_column] | date:'shortDate' $} - {$ i[f.sort_column] | number:f.num_digits $} - - {$ i.extra_data[f.sort_column] | number:f.num_digits $} - - - {$ i[f.sort_column] $} - {$ i[f.sort_column] | date:'shortDate' $} - {$ i[f.sort_column] | number:f.num_digits $} - - {$ i.extra_data[f.sort_column] | number:f.num_digits $} - - | -
Uploaded Green Button Files | +
---|
There are no green button files for this building | +
{$ f $} | +
Select columns from the list below to make them appear in your Buildings List table.
Column Name | |||
---|---|---|---|
- | - - | ++ | + + |
- - | -- {$ field.title $} - | -
+ + | ++ {$ field.title $} + | +
File types supported: .csv, .xls, .xlsx, and .xml.
+Note: multi-tabbed MS Excel files are not currently supported.
+An error occurred while processing the file. Please ensure that your file meets the required specifications.
+{$ step_12_error_message $}+
The DOE developed the Standard Energy Efficiency Data - (SEED) Platform as a free software tool that provides - a standardized format for collecting, storing and analyzing - building energy performance information about large - portfolios. Upload your buildings list to get started.
- -Upload your buildings list Getting Started Guide
+The DOE developed the Standard Energy Efficiency Data + (SEED) Platform as a free software tool that provides + a standardized format for collecting, storing and analyzing + building energy performance information about large + portfolios. Upload your buildings list to get started.
+ +Get started using SEED Platform by uploading your buildings - list (city tax assessor data) and then your EPA - Portfolio Manager data. Make sure these files are each - in .csv, .xls, .xlsx, or .xml format. The SEED - Platform will help you map and validate your data in - the process of loading.
+Get started using SEED Platform by uploading your buildings + list (city tax assessor data) and then your EPA + Portfolio Manager data. Make sure these files are each + in .csv, .xls, .xlsx, or .xml format. The SEED + Platform will help you map and validate your data in + the process of loading.
- + - -Match-up your buildings list with the Portfolio - Manager dataset to tie building records together. SEED - Platform will help you by auto-matching high confidence - pairings and then provide you with tools to match the - rest of your dataset.
- -Use SEED Platform's flexible, easy-to-use labeling system and project groupings to track the status of data submission, review, and compliance.
- -Match-up your buildings list with the Portfolio + Manager dataset to tie building records together. SEED + Platform will help you by auto-matching high confidence + pairings and then provide you with tools to match the + rest of your dataset.
+ +Use SEED Platform's flexible, easy-to-use labeling system and project groupings to track the status of data submission, review, and compliance.
+ +HOW THE SYSTEM AUTO-MATCHES YOUR BUILDINGS:
-Your source data file(s) are presented in the table on the left. All buildings where a possible data match exists are presented in a table on the right. The system attempts to auto-match building records using shared unique IDs like: PM Proprty ID, Tax Lot ID, and Custom IDs as well as Address information. Where the system believes a match exists between a record in your source file and an existing building record it will auto-check the 'match' checkbox — effectively making a match between these buildings. A confidence number (0 - 100%) is included to indicate the systems confidence that this match is correct.
-FIELDS NEEDED TO MAKE BUILDING MATCHES:
-The following is a list of the fields we use to match up buildings between different records. The more data you have in these four (4) fields the better our system will auto-match your buildings.
-Tax Lot ID PM Property ID
-Custom ID Address Line 1
-HOW TO MANUALLY MATCH YOUR BUILDINGS:
-You can review and edit each individual match by clicking one of the linked IDs in the table. This will open a new table that shows the source data for the individual building record you are attempting to match. Underneath this building is a searchable list of all potential existing building matches. Use this table to explore all your building records and to select single or multiple building records to match you source data to.
-VIEW/HIDE COLUMNS
-You can customize the columns displayed in the table below by clicking the 'View/Hide Columns' button and then selecting the column headers you want to review from the modal window.
-HOW THE SYSTEM AUTO-MATCHES YOUR BUILDINGS:
+Your source data file(s) are presented in the table on the left. All buildings where a possible data match exists are presented in a table on the right. The system attempts to auto-match building records using shared unique IDs like: PM Proprty ID, Tax Lot ID, and Custom IDs as well as Address information. Where the system believes a match exists between a record in your source file and an existing building record it will auto-check the 'match' checkbox — effectively making a match between these buildings. A confidence number (0 - 100%) is included to indicate the systems confidence that this match is correct.
+FIELDS NEEDED TO MAKE BUILDING MATCHES:
+The following is a list of the fields we use to match up buildings between different records. The more data you have in these four (4) fields the better our system will auto-match your buildings.
+Tax Lot ID PM Property ID
+Custom ID Address Line 1
+HOW TO MANUALLY MATCH YOUR BUILDINGS:
+You can review and edit each individual match by clicking one of the linked IDs in the table. This will open a new table that shows the source data for the individual building record you are attempting to match. Underneath this building is a searchable list of all potential existing building matches. Use this table to explore all your building records and to select single or multiple building records to match you source data to.
+VIEW/HIDE COLUMNS
+You can customize the columns displayed in the table below by clicking the 'View/Hide Columns' button and then selecting the column headers you want to review from the modal window.
+First name is required.
-Last name is required.
-Enter a valid email address.
-First name is required.
+Last name is required.
+Enter a valid email address.
+Select columns from the list below to make them appear in your Buildings List table.
Column Name | |||
---|---|---|---|
- | - - | ++ | + + |
- - | -- {$ field.title $} - | -
+ + | ++ {$ field.title $} + | +
As the admin of your SEED instance you can control what data is shared throughout your organization and between your sub-organizations as well as what data is shared externally with the public-at-large. The subset of data you choose to share with the public can be different than the subset shared between your sub-organizations.
-From the list below, select the fields that you want to: 1) share internally within your organization, and 2) share publicly with users outside your organization.
As the admin of your SEED instance you can control what data is shared throughout your organization and between your sub-organizations as well as what data is shared externally with the public-at-large. The subset of data you choose to share with the public can be different than the subset shared between your sub-organizations.
+From the list below, select the fields that you want to: 1) share internally within your organization, and 2) share publicly with users outside your organization.
+SHARE DATA WITH | @@ -42,21 +43,21 @@ | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
INTERNAL | -PUBLIC | +PUBLIC | Field Name | |||||||||
- - | -- - | -- - | ++ + | ++ + | ++ + |
Enter the minimum threshold count of buildings that can be returned in a shared query. The building count threshold is important for allowing other organizations to perform statistical analysis on your data without revealing information about individual buildings.
+Enter the minimum threshold count of buildings that can be returned in a shared query. The building count threshold is important for allowing other organizations to perform statistical analysis on your data without revealing information about individual buildings.
Please wait while your data is loaded...