diff --git a/seed/static/seed/js/controllers/column_settings_controller.js b/seed/static/seed/js/controllers/column_settings_controller.js index 0f20fd9822..f8e1c7c9ed 100644 --- a/seed/static/seed/js/controllers/column_settings_controller.js +++ b/seed/static/seed/js/controllers/column_settings_controller.js @@ -15,6 +15,8 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting 'auth_payload', 'columns_service', 'modified_service', + 'organization_service', + 'uploader_service', 'spinner_utility', 'urls', 'naturalSort', @@ -33,6 +35,8 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting auth_payload, columns_service, modified_service, + organization_service, + uploader_service, spinner_utility, urls, naturalSort, @@ -124,7 +128,7 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting $scope.matching_status = (column) => { if (column.is_extra_data) return 'ineligible'; - if ($scope.org.inventory_count && initial_matching_ids.includes(column.id)) return 'locked'; + if ($scope.org.access_level_names.length > 1 && initial_matching_ids.includes(column.id)) return 'locked'; return 'eligible'; }; @@ -247,6 +251,32 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting $scope.isModified = () => modified_service.isModified(); + $scope.complete_column_update = function () { + var matching_criteria_changed = _.find(_.values(diff), function (delta) { + return _.has(delta, 'is_matching_criteria'); + }); + + if (matching_criteria_changed) { + // reset the spinner and run whole org match merge link + spinner_utility.show(undefined, $('.display')[0]); + + organization_service.match_merge_link($scope.org.id, $scope.inventory_type).then(function (response) { + uploader_service.check_progress_loop( + response.progress_key, + 0, + 1, + (response) => { + organization_service.get_match_merge_link_result($scope.org.id, response.unique_id).then(() => column_update_complete()); + }, + () => {}, + { progress: 0 }, + ); + }) + } else { + column_update_complete(); + } + }; + // Table Sorting const default_sort_toggle = () => { $scope.column_sort = 'default'; @@ -350,6 +380,7 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting } modified_service.resetModified(); + $scope.modal_instance.dismiss(); $state.reload(); }; @@ -368,14 +399,7 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting return; } - const modal_instance = $scope.open_confirm_column_settings_modal(); - modal_instance.result - .then((data) => { - column_update_complete(data); - }) - .catch(() => { - // User cancelled - }); + $scope.modal_instance = $scope.open_confirm_column_settings_modal(); }; $scope.open_create_column_modal = () => $uibModal.open({ @@ -402,7 +426,8 @@ angular.module('SEED.controller.column_settings', []).controller('column_setting columns_service: () => columns_service, spinner_utility: () => spinner_utility, table_name: () => ($scope.inventory_type === 'properties' ? 'PropertyState' : 'TaxLotState'), - $q: () => $q + $q: () => $q, + complete_column_update: () => $scope.complete_column_update, } }); diff --git a/seed/static/seed/js/controllers/confirm_column_settings_modal_controller.js b/seed/static/seed/js/controllers/confirm_column_settings_modal_controller.js index 3369abd25e..adc067fc8d 100644 --- a/seed/static/seed/js/controllers/confirm_column_settings_modal_controller.js +++ b/seed/static/seed/js/controllers/confirm_column_settings_modal_controller.js @@ -15,7 +15,9 @@ angular.module('SEED.controller.confirm_column_settings_modal', []).controller(' 'proposed_changes', 'columns_service', 'spinner_utility', + 'uiGridGroupingConstants', '$q', + 'complete_column_update', '$interval', 'uploader_service', 'table_name', @@ -35,7 +37,9 @@ angular.module('SEED.controller.confirm_column_settings_modal', []).controller(' proposed_changes, columns_service, spinner_utility, + uiGridGroupingConstants, $q, + complete_column_update, $interval, uploader_service, table_name, @@ -194,7 +198,7 @@ angular.module('SEED.controller.confirm_column_settings_modal', []).controller(' organization_service.matching_criteria_columns($scope.org_id); } - $scope.confirm_changes_and_rehash = () => { + $scope.confirm_changes_and_rehash = () => { const api_ready_proposed_changes = Object.keys(proposed_changes).reduce((acc, col_id) => { const col = proposed_changes[col_id]; col.display_name = col.displayName; // Add display_name for backend @@ -220,6 +224,7 @@ angular.module('SEED.controller.confirm_column_settings_modal', []).controller(' 0, 1, (response) => { + complete_column_update(); $scope.result = `${response.message} in ${$scope.elapsed}`; $scope.state = 'done'; $interval.cancel($scope.interval); @@ -260,6 +265,217 @@ angular.module('SEED.controller.confirm_column_settings_modal', []).controller(' } }; + // Preview + // Agg function returning last value of matching criteria field (all should be the same if they match) + $scope.matching_field_value = function (aggregation, fieldValue) { + aggregation.value = fieldValue; + }; + + var prioritize_sort = function (grid, sortColumns) { + // To maintain grouping while giving users the ability to have some sorting, + // matching columns are given top priority followed by the hidden linking ID column. + // Lastly, non-matching columns are given next priority so that users can sort within a grouped set. + if (sortColumns.length > 1) { + var matching_cols = _.filter(sortColumns, function (col) { + return col.colDef.is_matching_criteria; + }); + var linking_id_col = _.find(sortColumns, ['name', 'id']); + var remaining_cols = _.filter(sortColumns, function (col) { + return !col.colDef.is_matching_criteria && !(col.name === 'id'); + }); + sortColumns = matching_cols.concat(linking_id_col).concat(remaining_cols); + _.forEach(sortColumns, function (col, index) { + col.sort.priority = index; + }); + } + }; + + // Takes raw cycle-partitioned records and returns array of cycle-aware records + var format_preview_records = function (raw_inventory) { + return _.reduce(raw_inventory, function (all_records, records, cycle_id) { + var cycle = _.find($scope.cycles, { id: parseInt(cycle_id) }); + _.forEach(records, function (record) { + record.cycle_name = cycle.name; + record.cycle_start = cycle.start; + all_records.push(record); + }); + return all_records; + }, []); + }; + + // Builds preview columns using non-extra_data columns + var build_preview_columns = function () { + // create copy in order to not change original column objects. + var preview_column_defs = _.reject(_.cloneDeep($scope.columns), 'is_extra_data'); + var default_min_width = 50; + var autopin_width = 100; + var column_def_defaults = { + headerCellFilter: 'translate', + minWidth: default_min_width, + width: 125, + groupingShowAggregationMenu: false + }; + + _.map(preview_column_defs, function (col) { + var options = {}; + if (col.data_type === 'datetime') { + options.cellFilter = 'date:\'yyyy-MM-dd h:mm a\''; + options.filter = inventory_service.dateFilter(); + } else { + options.filter = inventory_service.combinedFilter(); + } + + // For matching criteria values, always pin left and show values in aggregate rows. + if ($scope.proposed_matching_criteria_columns.includes(col.column_name)) { + col.pinnedLeft = true; + + // Help indicate matching columns are given preferred sort priority + col.displayName = col.displayName + '*'; + options.headerCellClass = 'matching-column-header'; + + options.customTreeAggregationFn = $scope.matching_field_value; + options.width = autopin_width; + } + return _.defaults(col, options, column_def_defaults); + }); + + // Grouping Settings + preview_column_defs.unshift( + { + displayName: 'Linking ID', + grouping: { groupPriority: 0 }, + name: 'id', + sort: { priority: 0, direction: 'desc' }, + pinnedLeft: true, + visible: false, + suppressRemoveSort: true, // since grouping relies on sorting + minWidth: default_min_width, + width: autopin_width + }, + { + name: 'cycle_name', + displayName: 'Cycle', + pinnedLeft: true, + treeAggregationType: uiGridGroupingConstants.aggregation.COUNT, + customTreeAggregationFinalizerFn: function (aggregation) { + aggregation.rendered = 'total cycles: ' + aggregation.value; + }, + minWidth: default_min_width, + width: autopin_width, + groupingShowAggregationMenu: false + }, + { + name: 'cycle_start', + displayName: 'Cycle Start', + cellFilter: 'date:\'yyyy-MM-dd\'', + filter: inventory_service.dateFilter(), + type: 'date', + sort: { priority: 1, direction: 'asc' }, + pinnedLeft: true, + minWidth: default_min_width, + width: autopin_width, + groupingShowAggregationMenu: false + } + ); + + return preview_column_defs; + }; + + // Initialize preview table as empty for now. + $scope.match_merge_link_preview = { + data: 'data', + enableColumnResizing: true, + enableFiltering: true, + onRegisterApi: function (gridApi) { + $scope.gridApi = gridApi; + + // used to allow filtering for child branches of grouping tree + $scope.gridApi.table_category = 'year-over-year'; + + $scope.gridApi.core.on.filterChanged($scope, function () { + // This is a workaround for losing the state of expanded rows during filtering. + _.delay($scope.gridApi.treeBase.expandAllRows, 500); + }); + + // Prioritized to maintain grouping. + $scope.gridApi.core.on.sortChanged($scope, prioritize_sort); + } + }; + + // Preview Loading Helpers + var build_proposed_matching_columns = function (result) { + // Summarize proposed matching_criteria_columns for pinning and to create preview + var criteria_additions = _.filter($scope.change_summary_data, function (change) { + return change.is_matching_criteria; + }); + var criteria_removals = _.filter($scope.change_summary_data, function (change) { + return change.is_matching_criteria === false; + }); + + $scope.criteria_changes = { + add: _.map(criteria_additions, 'column_name'), + remove: _.map(criteria_removals, 'column_name') + }; + + var base_and_add; + if ($scope.inventory_type == 'properties') { + base_and_add = _.union(result.PropertyState, $scope.criteria_changes.add); + } else { + base_and_add = _.union(result.TaxLotState, $scope.criteria_changes.add); + } + $scope.proposed_matching_criteria_columns = _.difference(base_and_add, $scope.criteria_changes.remove); + }; + + var build_preview = function (summary) { + $scope.data = format_preview_records(summary); + $scope.preview_columns = build_preview_columns(); + $scope.match_merge_link_preview.columnDefs = $scope.preview_columns; + }; + + var preview_loading_complete = function () { + $scope.preview_loading = false; + spinner_utility.hide(); + }; + + var get_preview = function () { + // Use new proposed matching_criteria_columns to request a preview then render this preview. + var spinner_options = { + scale: 0.40, + position: 'relative', + left: '100%' + }; + spinner_utility.show(spinner_options, $('#spinner_placeholder')[0]); + + organization_service.match_merge_link_preview($scope.org_id, $scope.inventory_type, $scope.criteria_changes) + .then(function (response) { + uploader_service.check_progress_loop( + response.progress_key, + 0, + 1, + (completion_notice) => { + organization_service.get_match_merge_link_result($scope.org_id, completion_notice.unique_id) + .then(build_preview) + .then(preview_loading_complete); + }, + () => resultHandler('error', 'Unexpected Error'), + { progress: 0 } + ); + }); + }; + + // Get and Show Preview (If matching criteria changes exist.) + $scope.matching_criteria_exists = _.find(_.values($scope.change_summary_data), function (delta) { + return _.has(delta, 'is_matching_criteria'); + }); + + if ($scope.matching_criteria_exists) { + $scope.preview_loading = true; + + organization_service.matching_criteria_columns($scope.org_id) + .then(build_proposed_matching_columns) + .then(get_preview); + } + $scope.setRunningState = () => { $scope.eta = $scope.etaFn(); if ($scope.eta) { diff --git a/seed/static/seed/js/services/organization_service.js b/seed/static/seed/js/services/organization_service.js index ebac53836a..7801fc8107 100644 --- a/seed/static/seed/js/services/organization_service.js +++ b/seed/static/seed/js/services/organization_service.js @@ -163,6 +163,42 @@ angular.module('SEED.service.organization', []).factory('organization_service', organization_factory.geocoding_columns = (org_id) => $http.get(`/api/v3/organizations/${org_id}/geocoding_columns/`).then((response) => response.data); organization_factory.reset_all_passwords = (org_id) => $http.post(`/api/v3/organizations/${org_id}/reset_all_passwords/`).then((response) => response.data); + + organization_factory.match_merge_link = function (org_id, inventory_type) { + return $http.post('/api/v3/organizations/' + org_id + '/match_merge_link/', { + inventory_type: inventory_type + }).then(function (response) { + return response.data; + }); + }; + + organization_factory.geocoding_columns = function (org_id) { + return $http.get('/api/v3/organizations/' + org_id + '/geocoding_columns/').then(function (response) { + return response.data; + }); + }; + + organization_factory.match_merge_link_preview = function (org_id, inventory_type, criteria_change_columns) { + return $http.post('/api/v3/organizations/' + org_id + '/match_merge_link_preview/', { + inventory_type: inventory_type, + add: criteria_change_columns.add, + remove: criteria_change_columns.remove + }).then(function (response) { + return response.data; + }); + }; + + organization_factory.get_match_merge_link_result = function (org_id, match_merge_link_id) { + return $http.get('/api/v3/organizations/' + org_id + '/match_merge_link_result/' + '?match_merge_link_id=' + match_merge_link_id).then(function (response) { + return response.data; + }); + }; + + organization_factory.reset_all_passwords = function (org_id) { + return $http.post('/api/v3/organizations/' + org_id + '/reset_all_passwords/').then(function (response) { + return response.data; + }); + }; organization_factory.insert_sample_data = (org_id) => $http.get(`/api/v3/organizations/${org_id}/insert_sample_data/`).then((response) => response.data); diff --git a/seed/static/seed/partials/confirm_column_settings_modal.html b/seed/static/seed/partials/confirm_column_settings_modal.html index 641ff047fd..58df13edb4 100644 --- a/seed/static/seed/partials/confirm_column_settings_modal.html +++ b/seed/static/seed/partials/confirm_column_settings_modal.html @@ -1,75 +1,51 @@ -