From 7c87dbe13da1954402291ff5a2b17d636a8fe82c Mon Sep 17 00:00:00 2001 From: Alex-At-Home Date: Thu, 26 Sep 2019 21:39:50 -0400 Subject: [PATCH 1/4] [#65] Backend logic to handle triggers and global queries, plus fix some old tests that had become broken --- src/server/models/TableModel.gs | 31 ++++---- src/server/services/ElasticsearchService.gs | 13 ++++ src/server/services/TableService.gs | 15 ++-- src/server/utils/ElasticsearchRequestUtils.gs | 12 ++++ .../utils/ElasticsearchResponseUtils.gs | 2 +- src/server/utils/TableRangeUtils.gs | 36 ++++++++++ .../services/TestElasticsearchService.gs | 72 ++++++++++++++++--- .../utils/TestElasticsearchResponseUtils.gs | 2 +- 8 files changed, 151 insertions(+), 32 deletions(-) diff --git a/src/server/models/TableModel.gs b/src/server/models/TableModel.gs index 474e748..379ba70 100644 --- a/src/server/models/TableModel.gs +++ b/src/server/models/TableModel.gs @@ -7,30 +7,27 @@ var defaultTableConfig_ = { // "timed": false, // "refresh_s": 60 // }, + "global_triggers": [], //array of ranges ("sheet!range") to include when deciding whether to refresh the table "query": { // "index_pattern": "tbd", "source": "none", - //, //points to field to use ("global", "local", "fixed") .. NOT_SUPPORTED: "global", "fixed" -// "global": { -// "range_name": "tbd" -// }, +//, //^points to field to use ("global", "local") + "global": { + "range_name": "sheet!range" + }, "local": { - "position": "top" //(or "bottom" ... NOT_SUPPORTED: "bottom") + "position": "top" //(or "bottom") } - //, -// "fixed": { -// "string": "{} or SQL or lucene" -// } }, "pagination": { "source": "none", - //, //points to field to use ("global", "local", "fixed") .. NOT_SUPPORTED: "global", "fixed" + //, //points to field to use ("global", "local") .. NOT_SUPPORTED: "global" // "global": { // "enabled": false, // "range_name": "tbd" // }, "local": { - "position": "bottom" //(or "top") .. NOT_SUPPORTED: "top" + "position": "bottom" //(or "top") } }, "status": { @@ -43,12 +40,12 @@ var defaultTableConfig_ = { "# eg -x.*.y / +x.y (start with -s where possible)", "# pre-built groups: $$", "#(note fields are laid out in match order)" - ], //(# to ignore an entry, [+-] to be +ve/-ve selection, // for regex else */** for single/multi path wildcard) + ], //(# to ignore an entry, [+-] to be +ve/-ve selection, // for regex else * for full wildcard) "exclude_filtered_fields_from_autocomplete": true, "autocomplete_filters": [ //(only affects autocomplete - eg for aggregations) "# eg x, -x.*.y, +x.y (start with -s if possible)", "# pre-built groups: $$" - ], //(# to ignore an entry, [+-] to be +ve/-ve selection, // for regex else */** for single/multi path wildcard) + ], //(# to ignore an entry, [+-] to be +ve/-ve selection, // for regex else * for full wildcard) "field_aliases": [ "#field.path=Alias To Use", "#(note fields are laid out in order within filter matches)" @@ -58,9 +55,9 @@ var defaultTableConfig_ = { "include_note": true, //(adds a note to the top left of each table with the name) "theme": "minimal" //(or "none", in the future: "default", etc) }, - "skip": { - "rows": "", //comma-separated list of offsets - "cols": "", //comma-separated list of offsets + "skip": { //NOT_SUPPORTED +// "rows": "", //comma-separated list of offsets +// "cols": "", //comma-separated list of offsets } //, // "rotated": false, //(left-to-right instead of top-to-bottom) @@ -86,7 +83,7 @@ var defaultTableConfig_ = { "size": "$$pagination_size", "_source": "$$field_filters" }, - "script_fields": [] + "script_fields": [] // format is { // name: "string", // the name used in the output column // source: "string", // the script itself diff --git a/src/server/services/ElasticsearchService.gs b/src/server/services/ElasticsearchService.gs index a31826f..bcf40a7 100644 --- a/src/server/services/ElasticsearchService.gs +++ b/src/server/services/ElasticsearchService.gs @@ -287,6 +287,7 @@ var ElasticsearchService_ = (function() { /** Trigger for edit */ function handleContentUpdates(event, triggerOverride) { + var ss = SpreadsheetApp.getActive() //(copy paste from ElasticsearchManager.isTriggerEnabled_) var isTriggerEnabled = function(tableConfig, trigger) { var tableTrigger = tableConfig.trigger || "control_change" @@ -316,6 +317,8 @@ var ElasticsearchService_ = (function() { var tableConfig = matchingTables[matchingTableName] var activeRange = tableConfig.activeRange delete tableConfig.activeRange //(remove extra non-standard field) + delete tableConfig.temp //(this is a copy of the meta so doesn't do anything) + //TODO: ^ really we should use the last table config that was _tested_? tableConfig = tableConfig.temp ? tableConfig.temp : tableConfig //(use current version, not saved) // Logic to determine if a table edit hits the control cells (query/page) @@ -336,6 +339,16 @@ var ElasticsearchService_ = (function() { return TableRangeUtils_.doRangesIntersect(event.range, newRange) }) + // Also handle any global triggers (including queries): + var globalTriggerRanges = TableRangeUtils_.getExternalTableRanges(ss, tableConfig) + modifiedOffsets = modifiedOffsets.concat( + globalTriggerRanges.filter(function(triggerRange) { + return TableRangeUtils_.doRangesIntersect(event.range, triggerRange) + }).map(function(rangeNotation) { + return "query_offset" //(so that will be treated like a control change) + }) + ) + if (modifiedOffsets.length > 0) { if (modifiedOffsets.indexOf("query_offset") >= 0) { //query has changed... //...if the page is hand specified, reset to 1 diff --git a/src/server/services/TableService.gs b/src/server/services/TableService.gs index 7246431..d2a2e59 100644 --- a/src/server/services/TableService.gs +++ b/src/server/services/TableService.gs @@ -173,11 +173,18 @@ var TableService_ = (function(){ var namedRangeMap = TableRangeUtils_.listTableRanges(ss, Object.keys(tableMap)) var retVal = {} Object.keys(namedRangeMap).forEach(function(tableName) { + var tableConfig = tableMap[tableName] + var globalTriggerRanges = TableRangeUtils_.getExternalTableRanges(ss, tableConfig) var namedRange = namedRangeMap[tableName] - if (TableRangeUtils_.doRangesIntersect(range, namedRange.getRange())) { - retVal[tableName] = TableRangeUtils_.shallowCopy(tableMap[tableName]) - if (addRange) { - retVal[tableName].activeRange = namedRange.getRange() + var allRangesToTest = globalTriggerRanges.concat([ namedRange.getRange() ]) + for (rangeToTestIndex in allRangesToTest) { + var rangeToTest = allRangesToTest[rangeToTestIndex] + if (TableRangeUtils_.doRangesIntersect(range, rangeToTest)) { + retVal[tableName] = TableRangeUtils_.shallowCopy(tableConfig) + if (addRange) { + retVal[tableName].activeRange = namedRange.getRange() + } + break //(no more checks needed, match gets qualified in calling method) } } }) diff --git a/src/server/utils/ElasticsearchRequestUtils.gs b/src/server/utils/ElasticsearchRequestUtils.gs index f1e28f1..6a935d5 100644 --- a/src/server/utils/ElasticsearchRequestUtils.gs +++ b/src/server/utils/ElasticsearchRequestUtils.gs @@ -122,6 +122,18 @@ var ElasticsearchRequestUtils_ = (function() { break } retVal.query_offset = { row: queryRow, col: 2 } + } else if ( //Global query, get from its external location: + "global" == TableRangeUtils_.getJson(tableConfig, [ "common", "query", "source" ]) + ) { + var ss = SpreadsheetApp.getActive() + var globalQueryRef = TableRangeUtils_.getJson( + tableConfig, [ "common", "query", "global", "range_name" ] + ) + var globalQueryRange = globalQueryRef ? + TableRangeUtils_.getRangeFromName(ss, globalQueryRef) : null + if (globalQueryRange) { + retVal.query = globalQueryRange.getCell(1, 1).getValue() + } } // Status (if not merged) diff --git a/src/server/utils/ElasticsearchResponseUtils.gs b/src/server/utils/ElasticsearchResponseUtils.gs index e2873c0..19f0db1 100644 --- a/src/server/utils/ElasticsearchResponseUtils.gs +++ b/src/server/utils/ElasticsearchResponseUtils.gs @@ -195,7 +195,7 @@ var ElasticsearchResponseUtils_ = (function() { if (debugMode && debugModeSwallowException) debug.push("ERROR: [" + arrayFieldChain + "] vs [" + mutableState.bottom_path + "], "); else throw new Error( "By policy, only allowed a single chain of nested aggregations - [" + arrayFieldChain + "] vs [" + mutableState.bottom_path + "], " + - "if you need intermediate buckets you can filter them out by setting 'filter_field' to '-' or '-**'" + "if you need intermediate buckets you can filter them out by setting 'filter_field' to '-' or '-*'" ) } else if (!mutableState.bottom_path) { mutableState.bottom_path = arrayFieldChain diff --git a/src/server/utils/TableRangeUtils.gs b/src/server/utils/TableRangeUtils.gs index 3393ad4..dae7312 100644 --- a/src/server/utils/TableRangeUtils.gs +++ b/src/server/utils/TableRangeUtils.gs @@ -354,6 +354,40 @@ return retVal } + /** For a given table, returns a list of ranges that the table monitors + * and treats like control changes + */ + function getExternalTableRanges(ss, tableConfig) { + var globalTriggers = TableRangeUtils_.getJson(tableConfig, [ "common", "global_triggers" ]) || [] + var isGlobalQuery = "global" == TableRangeUtils_.getJson(tableConfig, [ "common", "query", "source" ]) + var globalQuery = TableRangeUtils_.getJson(tableConfig, [ "common", "query", "global", "range_name" ]) + if (isGlobalQuery) { + globalTriggers = globalTriggers.concat([ globalQuery ]) + } + return globalTriggers.map(function(rangeOrNotation) { + return getRangeFromName(ss, rangeOrNotation) + }).filter(function(range) { + return range != null + }) + } + + /** Returns the range from a range notation + * TODO: needs to be able to handle named ranges (see getJsonLookup) + */ + function getRangeFromName(ss, rangeOrNotation) { + var isNotation = + rangeOrNotation.indexOf(":") >= 0 || /^[A-Z]+[0-9]+$/.exec(rangeOrNotation) + var isFullNotation = rangeOrNotation.indexOf("!") >= 0 + + return isNotation ? + (isFullNotation ? + ss.getRange(rangeOrNotation) + : + ss.getActiveSheet().getRange(rangeOrNotation) + ) + : null //(see above: also support named ranges, cf getJsonLookup) + } + //////////////////////////////////////////////////////// // 3] Internal utils @@ -402,6 +436,8 @@ formatDate: formatDate, fixSelectAllRanges: fixSelectAllRanges, doRangesIntersect: doRangesIntersect, + getExternalTableRanges: getExternalTableRanges, + getRangeFromName: getRangeFromName, TESTONLY: { diff --git a/test/server/services/TestElasticsearchService.gs b/test/server/services/TestElasticsearchService.gs index cdea6fd..4a0dfd5 100644 --- a/test/server/services/TestElasticsearchService.gs +++ b/test/server/services/TestElasticsearchService.gs @@ -105,6 +105,9 @@ "local": { "position": "none" }, + "global": { + "range_name": "TBD" + }, "source": "local" }, "pagination": { @@ -126,6 +129,7 @@ }} var defaultA1Notation = "A1:E10" + var externalQueryNotation = "H20:H20" TestService_.Utils.performTest(testResults, "no_special_rows_plus_check_es_meta", function() { var tableConfig = TestService_.Utils.deepCopyJson(baseTableConfig) @@ -212,7 +216,8 @@ "query_status_pagination": { status: "top", merge: true, noteMode: 2 }, "pagination_status": { status: "bottom", merge: true, noteMode: 3 }, "pagination_status_test": { status: "bottom", merge: true, testMode: true, noteMode: 3 }, - "query_status_pagination_nomerge": { status: "bottom", merge: false, noteMode: 4 } + "query_status_pagination_nomerge": { status: "bottom", merge: false, noteMode: 4 }, + "global_query": { noteMode: 4 } } var testRunner = function(testName, testConfig) { TestService_.Utils.performTest(testResults, testName, function() { @@ -223,7 +228,8 @@ var testMode = testConfig.testMode - var includeQueryBar = testName.indexOf("query") >= 0 + var globalQuery = testName.indexOf("global_query") >= 0 + var includeQueryBar = !globalQuery && testName.indexOf("query") >= 0 var includePagination = testName.indexOf("pagination") >= 0 var includeStatus = testName.indexOf("status") >= 0 @@ -241,7 +247,10 @@ default: return null }}()) var expectedNote = (function() { switch(testConfig.noteMode) { - case 1: return testMode ? "" : "ES Table: use_named_range\n" +//TODO: notes not supported by this test currently, they are explicitly disabled for unsaved tables +// case 1: return testMode ? "" : "ES Table: use_named_range\n" +// (for the other cases we were just checking that it didn't overwrite them anyway, so can leave) + case 1: return "" case 2: return "ES Table: use_named_range\nPlus user notes" case 3: return testMode ? "ES Table: use_named_range\n" : "" case 4: return "User added note" @@ -253,6 +262,12 @@ var testCaseInfoArray = [] var queryPosition = { col: 2 , row: 1 } tableConfig.common.formatting.include_note = includeNote + if (globalQuery) { + numTestCases *= Object.keys(queryTestCases).length + testCaseInfoArray.push(queryTestCases) + tableConfig.common.query.global.range_name = externalQueryNotation + tableConfig.common.query.source = "global" + } if (includeQueryBar) { expectedDataSize-- numTestCases *= Object.keys(queryTestCases).length @@ -295,6 +310,9 @@ for (var testCaseKey in testCases) { var testCaseConfig = testCases[testCaseKey] range.clear() + if (globalQuery) { + testSheet.getRange(externalQueryNotation).setValue(testCaseConfig.query) + } if (includeQueryBar) { // query always set - default means "", so write to the right spot range.getCell(1, 1).setValue("Query:") @@ -497,11 +515,11 @@ var resetTables = function() { testSheet.getRange("B2").setValue("test status") testSheet.getRange("B5").setValue(2) - ManagementService_.updateTempSavedObject("testa", "testa", null) + ManagementService_.updateSavedObject("testa", TableRangeUtils_.shallowCopy(baseTableConfig)) ManagementService_.setSavedObjectTrigger("testa", "") testSheet.getRange("G2").setValue("test status") testSheet.getRange("G5").setValue(2) - ManagementService_.updateTempSavedObject("testb", "testb", null) + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(baseTableConfig)) ManagementService_.setSavedObjectTrigger("testb", "") } @@ -524,6 +542,12 @@ var contentConfig = TestService_.Utils.deepCopyJson(baseTableConfig) contentConfig.trigger = "content_change" + var triggerConfig = TestService_.Utils.deepCopyJson(baseTableConfig) + triggerConfig.common.global_triggers = [ "B25:C25" ] + triggerConfig.common.query.source = "global" + triggerConfig.common.query.global.range_name = "A25" + triggerConfig.trigger = "control_change" + // Now check triggers: // page of table 1 @@ -540,7 +564,7 @@ //(Add a temp object with pagination turned off) resetTables() - ManagementService_.updateTempSavedObject("testa", "testa", noPagination) + ManagementService_.updateSavedObject("testa", TableRangeUtils_.shallowCopy(noPagination)) var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) TestService_.Utils.assertEquals(0, retVal, "retval (nopage)") @@ -551,7 +575,7 @@ // Nothing should happen if it's disabled resetTables() - ManagementService_.updateTempSavedObject("testa", "testa", disabledConfig) + ManagementService_.updateSavedObject("testa", TableRangeUtils_.shallowCopy(disabledConfig)) var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) TestService_.Utils.assertEquals(0, retVal, "retval (nopage)") @@ -571,7 +595,7 @@ tests.forEach(function(testConfig) { resetTables() if (testConfig.temp) { - ManagementService_.updateTempSavedObject("testb", "testb", noPagination) + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(noPagination)) } if (testConfig.formula) { testSheet.getRange("G5").setFormula("=A10") @@ -595,7 +619,7 @@ TestService_.Utils.performTest(testResults, "Content intersection vs [" + contentEnabled + "]", function() { resetTables() if (contentEnabled) { - ManagementService_.updateTempSavedObject("testb", "testb", contentConfig) + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(contentConfig)) } var changeEvent = { range: testSheet.getRange("A3:Z3") } var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) @@ -619,6 +643,36 @@ } }) }) + + TestService_.Utils.performTest(testResults, "Global trigger", function() { + // Global query trigger + resetTables() + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(triggerConfig)) + testSheet.getRange("G1").setValue("test status") //(no query bar) + var changeEvent = { range: testSheet.getRange("A22:A26") } + var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) + TestService_.Utils.assertEquals( + 1, retVal, "check handleContentUpdates return value (triggerConfig, test 1)" + ) + var resultsB = getResults("testb", "F1:J5", "G1", "G5") + TestService_.Utils.assertEquals( + { p: 1, s: "AWAITING REFRESH", t: "control_change"}, resultsB[0], "tableB1=[" + resultsB[1] + "]" + ) + + // Other global triggers + resetTables() + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(triggerConfig)) + testSheet.getRange("G1").setValue("test status") //(no query bar) + var changeEvent = { range: testSheet.getRange("B22:B26") } + var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) + TestService_.Utils.assertEquals( + 1, retVal, "check handleContentUpdates return value (triggerConfig, test 2)" + ) + var resultsB = getResults("testb", "F1:J5", "G1", "G5") + TestService_.Utils.assertEquals( + { p: 1, s: "AWAITING REFRESH", t: "control_change"}, resultsB[0], "tableB2=[" + resultsB[1] + "]" + ) + }) } //////////////////////////////////////////////////////// diff --git a/test/server/utils/TestElasticsearchResponseUtils.gs b/test/server/utils/TestElasticsearchResponseUtils.gs index 22803c3..c404bb5 100644 --- a/test/server/utils/TestElasticsearchResponseUtils.gs +++ b/test/server/utils/TestElasticsearchResponseUtils.gs @@ -166,7 +166,7 @@ var TestElasticsearchResponseUtils_ = (function() { } var mr1 = { "name": "mr1" } if (!config.mr_enabled) { - mr1.field_filter = "-**" + mr1.field_filter = "-*" } var m2 = { "name": "m2", "field_filter": "-stat2.filter_out" } if (config.alt_filter) { From 0f758abc0e2cff5b1747ac515fa7ce597e2108c0 Mon Sep 17 00:00:00 2001 From: Alex-At-Home Date: Sat, 28 Sep 2019 11:36:14 -0400 Subject: [PATCH 2/4] [#65] Add support for external status cell --- src/server/models/TableModel.gs | 10 ++++-- src/server/utils/ElasticsearchRequestUtils.gs | 5 ++- .../utils/ElasticsearchResponseUtils.gs | 31 +++++++++++-------- src/server/utils/TableRangeUtils.gs | 19 ++++++++++++ .../services/TestElasticsearchService.gs | 23 ++++++++++++-- 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/src/server/models/TableModel.gs b/src/server/models/TableModel.gs index 379ba70..b2f541c 100644 --- a/src/server/models/TableModel.gs +++ b/src/server/models/TableModel.gs @@ -7,13 +7,14 @@ var defaultTableConfig_ = { // "timed": false, // "refresh_s": 60 // }, +//TODO: have content_global_triggers AND control_global_triggers "global_triggers": [], //array of ranges ("sheet!range") to include when deciding whether to refresh the table "query": { // "index_pattern": "tbd", "source": "none", //, //^points to field to use ("global", "local") "global": { - "range_name": "sheet!range" + "range_name": "[sheet!]range" }, "local": { "position": "top" //(or "bottom") @@ -31,8 +32,11 @@ var defaultTableConfig_ = { } }, "status": { - "position": "top", //(or "bottom", "none") - "merge": false //(if false will be its own separate line, else will merge with query/pagination if they exist) + "position": "top", //(or "bottom", "none", "global") + "merge": false, //(if false will be its own separate line, else will merge with query/pagination if they exist) + "global": { + "range_name": "[sheet!]range" + } }, "headers": { "position": "top", //(or "bottom", "top_bottom", "none") .. NOT_SUPPORTED: "bottom", "top_bottom" diff --git a/src/server/utils/ElasticsearchRequestUtils.gs b/src/server/utils/ElasticsearchRequestUtils.gs index 6a935d5..87a8042 100644 --- a/src/server/utils/ElasticsearchRequestUtils.gs +++ b/src/server/utils/ElasticsearchRequestUtils.gs @@ -169,7 +169,10 @@ var ElasticsearchRequestUtils_ = (function() { statusCells.merge() break } - } + } else if (!testMode) { // Check for global status + var ss = SpreadsheetApp.getActive() + TableRangeUtils_.handleGlobalStatusInfo(ss, statusInfo, tableConfig) + } // Headers diff --git a/src/server/utils/ElasticsearchResponseUtils.gs b/src/server/utils/ElasticsearchResponseUtils.gs index 19f0db1..f5dfdaa 100644 --- a/src/server/utils/ElasticsearchResponseUtils.gs +++ b/src/server/utils/ElasticsearchResponseUtils.gs @@ -325,14 +325,16 @@ var ElasticsearchResponseUtils_ = (function() { json, rows, fullCols, supportsSize, numHits, numHitsOperator ) { - var ss = SpreadsheetApp.getActive() - var tableRange = TableRangeUtils_.findTableRange(ss, tableName) - var range = null - if (null == tableRange) { //(use current selection, test mode + var ss = SpreadsheetApp.getActive() + var tableRange = TableRangeUtils_.findTableRange(ss, tableName) + var range = null + if (null == tableRange) { //(use current selection, test mode range = ss.getActiveRange() - } else { - range = tableRange.getRange() - } + } else { + range = tableRange.getRange() + } + var globalStatus = + "global" == TableRangeUtils_.getJson(tableConfig, [ "common", "status", "position" ]) if (null != json.response) { /** Apply the global filters to the cols and re-order as desired */ @@ -490,13 +492,13 @@ var ElasticsearchResponseUtils_ = (function() { } // Write warnings to status (never to toaster) - if (context.table_meta.status_offset) { + if (context.table_meta.status_offset || globalStatus) { var warningText = "" if (warnings.length > 0) { warningText = ": (WARNINGS = " + warnings.map(function(x) { return "[" + x + "]" }).join(", ") + ")" } setQueryResponseInStatus_(range, context.table_meta.status_offset, - "SUCCESS [" + TableRangeUtils_.formatDate() + "]" + warningText) + "SUCCESS [" + TableRangeUtils_.formatDate() + "]" + warningText, tableConfig) } } else if (null != json.error_message) { // Write errors to status or toaster @@ -505,8 +507,8 @@ var ElasticsearchResponseUtils_ = (function() { : "ERROR [" + TableRangeUtils_.formatDate() + "]" + ": status = [" + json.status + "], msg = [" + json.error_message + "], query = [" + json.query_string + "]" - if (context.table_meta.status_offset) { - setQueryResponseInStatus_(range, context.table_meta.status_offset, requestError) + if (context.table_meta.status_offset || globalStatus) { + setQueryResponseInStatus_(range, context.table_meta.status_offset, requestError, tableConfig) } else { // pop up toaster showStatus("[" + tableName + "]: " + requestError, "Query Error") } @@ -685,8 +687,11 @@ var ElasticsearchResponseUtils_ = (function() { } /** Adds the error info the status, if necessary */ - function setQueryResponseInStatus_(range, statusLocation, errorString) { - range.getCell(statusLocation.row, statusLocation.col).setValue(errorString) + function setQueryResponseInStatus_(range, statusLocation, errorString, tableConfig) { + var ss = SpreadsheetApp.getActive() + if (!TableRangeUtils_.handleGlobalStatusInfo(ss, errorString, tableConfig)) { + range.getCell(statusLocation.row, statusLocation.col).setValue(errorString) + } } //////////////////////////////////////////////////////// diff --git a/src/server/utils/TableRangeUtils.gs b/src/server/utils/TableRangeUtils.gs index dae7312..6f2a42a 100644 --- a/src/server/utils/TableRangeUtils.gs +++ b/src/server/utils/TableRangeUtils.gs @@ -388,6 +388,24 @@ : null //(see above: also support named ranges, cf getJsonLookup) } + /** Utility function to write global status info out - returns true iff status _is_ global */ + function handleGlobalStatusInfo(ss, statusInfo, tableConfig) { + if ( //Global query, get from its external location: + "global" == TableRangeUtils_.getJson(tableConfig, [ "common", "status", "position" ]) + ) { + var globalSourceRef = TableRangeUtils_.getJson( + tableConfig, [ "common", "status", "global", "range_name" ] + ) + var globalSourceRange = globalSourceRef ? + TableRangeUtils_.getRangeFromName(ss, globalSourceRef) : null + if (globalSourceRange) { + globalSourceRange.getCell(1, 1).setValue(statusInfo) + return true + } + } + return false + } + //////////////////////////////////////////////////////// // 3] Internal utils @@ -438,6 +456,7 @@ doRangesIntersect: doRangesIntersect, getExternalTableRanges: getExternalTableRanges, getRangeFromName: getRangeFromName, + handleGlobalStatusInfo: handleGlobalStatusInfo, TESTONLY: { diff --git a/test/server/services/TestElasticsearchService.gs b/test/server/services/TestElasticsearchService.gs index 4a0dfd5..4b98df2 100644 --- a/test/server/services/TestElasticsearchService.gs +++ b/test/server/services/TestElasticsearchService.gs @@ -121,7 +121,10 @@ }, "status": { "position": "none", - "merge": false + "merge": false, + "global": { + "range_name": "TBD" + } }, "formatting": { "theme": "minimal" @@ -130,6 +133,7 @@ var defaultA1Notation = "A1:E10" var externalQueryNotation = "H20:H20" + var externalStatusNotation = "J22" TestService_.Utils.performTest(testResults, "no_special_rows_plus_check_es_meta", function() { var tableConfig = TestService_.Utils.deepCopyJson(baseTableConfig) @@ -217,7 +221,7 @@ "pagination_status": { status: "bottom", merge: true, noteMode: 3 }, "pagination_status_test": { status: "bottom", merge: true, testMode: true, noteMode: 3 }, "query_status_pagination_nomerge": { status: "bottom", merge: false, noteMode: 4 }, - "global_query": { noteMode: 4 } + "global_query_global_status": { noteMode: 4 } } var testRunner = function(testName, testConfig) { TestService_.Utils.performTest(testResults, testName, function() { @@ -229,9 +233,10 @@ var testMode = testConfig.testMode var globalQuery = testName.indexOf("global_query") >= 0 + var globalStatus = testName.indexOf("global_status") >= 0 var includeQueryBar = !globalQuery && testName.indexOf("query") >= 0 var includePagination = testName.indexOf("pagination") >= 0 - var includeStatus = testName.indexOf("status") >= 0 + var includeStatus = !globalStatus && testName.indexOf("status") >= 0 // Note tests: // 1: add note, replaces no note @@ -284,6 +289,10 @@ tableConfig.common.pagination.source = "local" } var statusPosition = { } + if (globalStatus) { + tableConfig.common.status.position = "global" + tableConfig.common.status.global.range_name = externalStatusNotation + } if (includeStatus) { if (!testConfig.merge) { expectedDataSize-- @@ -387,6 +396,14 @@ TestService_.Utils.assertEquals(expectedPage, range.getCell(pagePosition.row, pagePosition.col).getValue(), "page value" + extraTestCaseConfig) } } + if (globalStatus) { + var statusShouldBePending = testSheet.getRange(externalStatusNotation).getValue() + if (testMode) { + TestService_.Utils.assertEquals("", statusShouldBePending, "status value: " + statusShouldBePending + extraTestCaseConfig) + } else { + TestService_.Utils.assertEquals(true, 0 == statusShouldBePending.indexOf("PENDING"), "status value: " + statusShouldBePending + extraTestCaseConfig) + } + } if (includeStatus) { var statusShouldBePending = range.getCell(statusPosition.row, statusPosition.col).getValue() if (testMode) { From 528904b2c4cd51792a2b80230c375dec1239bee6 Mon Sep 17 00:00:00 2001 From: Alex-At-Home Date: Sat, 28 Sep 2019 12:13:10 -0400 Subject: [PATCH 3/4] [#65] Split global triggers into content and control so we can identify when table has been changed without immediately refreshing it --- src/server/models/TableModel.gs | 4 ++-- src/server/services/ElasticsearchService.gs | 3 ++- src/server/services/TableService.gs | 3 ++- src/server/utils/TableRangeUtils.gs | 8 +++++-- .../services/TestElasticsearchService.gs | 21 +++++++++++++++++-- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/server/models/TableModel.gs b/src/server/models/TableModel.gs index b2f541c..f7e8b74 100644 --- a/src/server/models/TableModel.gs +++ b/src/server/models/TableModel.gs @@ -7,8 +7,8 @@ var defaultTableConfig_ = { // "timed": false, // "refresh_s": 60 // }, -//TODO: have content_global_triggers AND control_global_triggers - "global_triggers": [], //array of ranges ("sheet!range") to include when deciding whether to refresh the table + "global_content_triggers": [], //array of ranges ("sheet!range") to include when deciding whether to mark the table as edited + "global_control_triggers": [], //array of ranges ("sheet!range") to include when deciding whether to refresh the table "query": { // "index_pattern": "tbd", "source": "none", diff --git a/src/server/services/ElasticsearchService.gs b/src/server/services/ElasticsearchService.gs index bcf40a7..47b65c8 100644 --- a/src/server/services/ElasticsearchService.gs +++ b/src/server/services/ElasticsearchService.gs @@ -340,7 +340,8 @@ var ElasticsearchService_ = (function() { }) // Also handle any global triggers (including queries): - var globalTriggerRanges = TableRangeUtils_.getExternalTableRanges(ss, tableConfig) + var globalTriggerRanges = + TableRangeUtils_.getExternalTableRanges(ss, tableConfig, /*controlOnly*/true) modifiedOffsets = modifiedOffsets.concat( globalTriggerRanges.filter(function(triggerRange) { return TableRangeUtils_.doRangesIntersect(event.range, triggerRange) diff --git a/src/server/services/TableService.gs b/src/server/services/TableService.gs index d2a2e59..cc44044 100644 --- a/src/server/services/TableService.gs +++ b/src/server/services/TableService.gs @@ -174,7 +174,8 @@ var TableService_ = (function(){ var retVal = {} Object.keys(namedRangeMap).forEach(function(tableName) { var tableConfig = tableMap[tableName] - var globalTriggerRanges = TableRangeUtils_.getExternalTableRanges(ss, tableConfig) + var globalTriggerRanges = + TableRangeUtils_.getExternalTableRanges(ss, tableConfig, /*controlOnly*/false) var namedRange = namedRangeMap[tableName] var allRangesToTest = globalTriggerRanges.concat([ namedRange.getRange() ]) for (rangeToTestIndex in allRangesToTest) { diff --git a/src/server/utils/TableRangeUtils.gs b/src/server/utils/TableRangeUtils.gs index 6f2a42a..f6c28ae 100644 --- a/src/server/utils/TableRangeUtils.gs +++ b/src/server/utils/TableRangeUtils.gs @@ -357,8 +357,12 @@ /** For a given table, returns a list of ranges that the table monitors * and treats like control changes */ - function getExternalTableRanges(ss, tableConfig) { - var globalTriggers = TableRangeUtils_.getJson(tableConfig, [ "common", "global_triggers" ]) || [] + function getExternalTableRanges(ss, tableConfig, controlOnly) { + var globalControlTriggers = + (TableRangeUtils_.getJson(tableConfig, [ "common", "global_control_triggers" ]) || []) + var globalContentTriggers = !controlOnly ? + (TableRangeUtils_.getJson(tableConfig, [ "common", "global_content_triggers" ]) || []) : [] + var globalTriggers = globalControlTriggers.concat(globalContentTriggers) var isGlobalQuery = "global" == TableRangeUtils_.getJson(tableConfig, [ "common", "query", "source" ]) var globalQuery = TableRangeUtils_.getJson(tableConfig, [ "common", "query", "global", "range_name" ]) if (isGlobalQuery) { diff --git a/test/server/services/TestElasticsearchService.gs b/test/server/services/TestElasticsearchService.gs index 4b98df2..d080a7b 100644 --- a/test/server/services/TestElasticsearchService.gs +++ b/test/server/services/TestElasticsearchService.gs @@ -560,7 +560,8 @@ contentConfig.trigger = "content_change" var triggerConfig = TestService_.Utils.deepCopyJson(baseTableConfig) - triggerConfig.common.global_triggers = [ "B25:C25" ] + triggerConfig.common.global_control_triggers = [ "B25:C25" ] + triggerConfig.common.global_content_triggers = [ testSheet.getName() + "!H25:H26" ] triggerConfig.common.query.source = "global" triggerConfig.common.query.global.range_name = "A25" triggerConfig.trigger = "control_change" @@ -676,7 +677,9 @@ { p: 1, s: "AWAITING REFRESH", t: "control_change"}, resultsB[0], "tableB1=[" + resultsB[1] + "]" ) - // Other global triggers + // Other global triggers: + + // Control: resetTables() ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(triggerConfig)) testSheet.getRange("G1").setValue("test status") //(no query bar) @@ -689,6 +692,20 @@ TestService_.Utils.assertEquals( { p: 1, s: "AWAITING REFRESH", t: "control_change"}, resultsB[0], "tableB2=[" + resultsB[1] + "]" ) + + // Content: + resetTables() + ManagementService_.updateSavedObject("testb", TableRangeUtils_.shallowCopy(triggerConfig)) + testSheet.getRange("G1").setValue("test status") //(no query bar) + var changeEvent = { range: testSheet.getRange("H25:H26") } + var retVal = ElasticsearchService_.handleContentUpdates(changeEvent, /*triggerOverride*/null) + TestService_.Utils.assertEquals( + 0, retVal, "check handleContentUpdates return value (triggerConfig, test 3)" + ) + var resultsB = getResults("testb", "F1:J5", "G1", "G5") + TestService_.Utils.assertEquals( + { p: 2, s: "HAND EDITED", t: ""}, resultsB[0], "tableB3=[" + resultsB[1] + "]" + ) }) } From 5109e44b596dcfb1b894592b80fee8d2179e96af Mon Sep 17 00:00:00 2001 From: Alex-At-Home Date: Sat, 28 Sep 2019 13:36:36 -0400 Subject: [PATCH 4/4] [#65] Add minimal UI support for global cell references for query/status (use JSON still) --- .../view-models/sidebarAppGeneralEditor.js | 14 +++++++++++--- src/frontend/view-models/sidebarAppTableForm.js | 1 - 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/frontend/view-models/sidebarAppGeneralEditor.js b/src/frontend/view-models/sidebarAppGeneralEditor.js index c214104..e218b2e 100644 --- a/src/frontend/view-models/sidebarAppGeneralEditor.js +++ b/src/frontend/view-models/sidebarAppGeneralEditor.js @@ -19,6 +19,7 @@ var GeneralEditor = (function(){ + ` @@ -61,6 +62,7 @@ var GeneralEditor = (function(){ +
@@ -110,6 +112,8 @@ var GeneralEditor = (function(){ var querySource = Util.getJson(json, ["common", "query", "source"]) || "none" if (querySource == "none") { $(`#querybar_${tableType}_${index}`).val("none") + } else if (querySource == "global") { + $(`#querybar_${tableType}_${index}`).val("global") } else { var localQueryPos = Util.getJson(json, ["common", "query", "local", "position"]) || "none" $(`#querybar_${tableType}_${index}`).val(localQueryPos) @@ -162,9 +166,13 @@ var GeneralEditor = (function(){ var thisValue = this.value Util.updateRawJsonNow(globalEditor, function(currJson) { var query = Util.getOrPutJsonObj(currJson, [ "common", "query" ]) - query.source = (thisValue == "none") ? "none" : "local" // (only supported option currently) - var localQuery = Util.getOrPutJsonObj(currJson, [ "common", "query", "local" ]) - localQuery.position = thisValue + if (("none" == thisValue) || ("global" == thisValue)) { + query.source = thisValue + } else { + query.source = "local" + var localQuery = Util.getOrPutJsonObj(currJson, [ "common", "query", "local" ]) + localQuery.position = thisValue + } }) }) } diff --git a/src/frontend/view-models/sidebarAppTableForm.js b/src/frontend/view-models/sidebarAppTableForm.js index c4623db..1f1deed 100644 --- a/src/frontend/view-models/sidebarAppTableForm.js +++ b/src/frontend/view-models/sidebarAppTableForm.js @@ -103,7 +103,6 @@ var TableForm = (function() { ` }//(endif standaloneEdit) - var panelHeader = '' var panelHeader = '' var panelInOrOut = standaloneEdit ? 'in' : 'out' if (!standaloneEdit) {