How do I show selected log level? - javascript

I'm currently making a select box to show all the log messages, and I'm struggling with getting it to show all the log messages for the selected value. I can get the log messages for the individual values, so when you select "Info" it shows all the messages under "Info" level
However my question is how do I select a log level higher to show that log level and the log levels below that level?
How I want it to work:
Log messages come from my database, these are for application message logging for support useage
"Debug" then have it select "Debug, Error, Message, Info"
"Error" then have it select "Error, Message, Info"
"Message" then have it select "Message, Info"
"Info" then have it select "Info"
Javascript Code
FilterLoggingTable();
function FilterLoggingTable()
{
// Check the value from the select box and filter out table data based on value selected
if ($("#loggingInputs").val() != null || $("#loggingDatePicker").val() != null)
{
var searchLoggingText = document.getElementById("loggingInputs").value.trim();
if (document.getElementById("loggingDatePicker") != null)
{
var searchLoggingDate = document.getElementById("loggingDatePicker").value.trim();
}
}
if (typeof GetServerLogging == 'function')
{
GetServerLogging(searchLoggingText, searchLoggingDate);
}
// Calls search logging function 2.5 seconds after page is ready
setTimeout(function ()
{
$("tr:not(:has(>th))").show().filter(function ()
{
var tableRowElement = this;
var tableRowTextFound, dateSelectedFound;
tableRowTextFound = (tableRowElement.textContent || tableRowElement.innerText || '').indexOf((searchLoggingText || "")) == -1;
dateSelectedFound = (tableRowElement.textContent || tableRowElement.innerText || '').indexOf((searchLoggingDate || "")) == -1;
return (tableRowTextFound || dateSelectedFound);
}).hide();
}, 1000);
};
// Get all server logging, and append to logging table (Logging page)
function GetServerLogging(loggingLevel, loggingDate) {
$.post("php/getServerLogging.php", {
command: "GetServerLogging",
getServerLoggingLevel: loggingLevel,
getServerLoggingDate: loggingDate
})
.success(function(data) {
var jsonMessage = JSON.parse(data);
var dataTable = $('#dataTables-example').DataTable();
dataTable.rows().remove().draw();
// Check to see if response message returns back "OK"
if (jsonMessage.RESPONSE == 'OK') {
$('#dataTables-example td').remove();
$("#dataTables-example tr:empty").remove();
// Set time before records start to load on page
setTimeout(function() {
console.log(JSON.stringify(data));
// Loops through the returned records
for (var i = 0; i < jsonMessage.RECORDS.length; i++) {
var currentRecord = jsonMessage.RECORDS[i];
var selectRecord = JSON.stringify(currentRecord);
var serverLoggingTableBody = $('#dataTables-example').children('tbody');
var serverLoggingTable = serverLoggingTableBody.length ? serverLoggingTableBody : $('#dataTables-example');
// Check to see if server log text doesn't equal to "Successfully synchronised"
if (currentRecord['ServerLogText'] != "Successfully synchronised") {
dataTable.row.add( [
GetCurrentDateUKFormatFromSQL(currentRecord['ServerLogDateTime']['date']),
currentRecord['ServerLogLevel'],
currentRecord['ServerLogText']
] ).draw();
}
}
}, 1000);
}
})
.fail(function(error) {
console.log("Unable to retrieve data from the server");
});
}
PHP Code
if (isset($_POST['getServerLoggingLevel']) && isset($_POST['command']) && $_POST['command'] == "GetServerLogging") {
// Set server logging level to a variable to be used in MSSQL query
$getServerLog = $_POST['getServerLoggingLevel'];
// Set server logging data to a variable to be used in MSSQL query
$getServerLogDate = $_POST['getServerLoggingDate'];
// Create a string format for server logging date
$originalDate = $getServerLogDate;
$newDate = date("Y-m-d", strtotime($originalDate));
// Use logging information to query to database to get the correct data back
$SyncServerFunctionList->RespondWithUnencryptedJSONMessage("OK", $SyncServerFunctionList->MSSQLExecuteSelectAndReturnResultArray($connection, "SELECT Logs.Origin as ServerLogUser, Logs.Text as ServerLogText,
Logs.Type as ServerLogLevel, Logs.Timestamp as ServerLogDateTime from Logs
where Logs.Type like '$getServerLog' and CONVERT(varchar(10), Logs.Timestamp, 120) like '$newDate'
ORDER BY Timestamp ASC", "", "failed to select registered midwives"));
}

You may need using SQL to select multiple value like
SELECT *****
FROM recordtable
WHERE warninglevel IN("Error","warning" )
IN operator will let you select a field match multiple values. the example let you select logs for "Error" and "warning" level. You could do other selection like this.

Related

Service Worker only showing first push notification (from cloud messaging) until I reload - message IS received by the worker

I'm trying to use this Web Push walk-through to allow my customers to get push notifications sent to their phones/desktops: https://framework.realtime.co/demo/web-push/
The demo on the site is working for me, and when I copy it over to my server I'm able to push messages down and I see them being logged in the JavaScript console by my service worker with every push down the channel.
However, only the FIRST message pushed down the channel is causing a notification to appear, the rest simply don't show up. If I revoke the service-worker and reload the page (to get a new one) it works again -- for 1 push.
I'm using the same ortc.js file they are, an almost identical service-worker.js, modified with the ability to pass JSON for image/URL options. My modified service worker code is below.
I'm not getting any errors in the JS console (the 2 in the image above were from something else), but I am getting a red x icon next to the service worker, though the number next to it doesn't seem to be tied to anything I can tell (and clicking it does nothing; clicking the service-worker.js side just drops me to line 1 of the service-worker.js file, below.
My question is: why am I getting the first notification, but not any others? Or how can I go about debugging it? My JS console is showing the payloads, and stepping through the JS with breakpoints has me getting lost in the minified firebase code (I have tried both 3.5 and 6.5 for the firebase.js files).
Here is my service worker:
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '580405122074'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const fb_messaging = firebase.messaging();
// Buffer to save multipart messages
var messagesBuffer = {};
// Gets the number of keys in a dictionary
var countKeys = function (dic) {
var count = 0;
for (var i in dic) {
count++;
}
return count;
};
// Parses the Realtime messages using multipart format
var parseRealtimeMessage = function (message) {
// Multi part
var regexPattern = /^(\w[^_]*)_{1}(\d*)-{1}(\d*)_{1}([\s\S.]*)$/;
var match = regexPattern.exec(message);
var messageId = null;
var messageCurrentPart = 1;
var messageTotalPart = 1;
var lastPart = false;
if (match && match.length > 0) {
if (match[1]) {
messageId = match[1];
}
if (match[2]) {
messageCurrentPart = match[2];
}
if (match[3]) {
messageTotalPart = match[3];
}
if (match[4]) {
message = match[4];
}
}
if (messageId) {
if (!messagesBuffer[messageId]) {
messagesBuffer[messageId] = {};
}
messagesBuffer[messageId][messageCurrentPart] = message;
if (countKeys(messagesBuffer[messageId]) == messageTotalPart) {
lastPart = true;
}
}
else {
lastPart = true;
}
if (lastPart) {
if (messageId) {
message = "";
// Aggregate all parts
for (var i = 1; i <= messageTotalPart; i++) {
message += messagesBuffer[messageId][i];
delete messagesBuffer[messageId][i];
}
delete messagesBuffer[messageId];
}
return message;
} else {
// We don't have yet all parts, we need to wait ...
return null;
}
}
// Shows a notification
function showNotification(message, settings) {
// In this example we are assuming the message is a simple string
// containing the notification text. The target link of the notification
// click is fixed, but in your use case you could send a JSON message with
// a link property and use it in the click_url of the notification
// The notification title
var notificationTitle = 'Web Push Notification';
var title = "Company Name";
var icon = "/img/default.png";
var url = "https://www.example.com/";
var tag = "same";
if(settings != undefined) {
if(hasJsonStructure(settings)) settings = JSON.parse(settings);
title = settings.title;
icon = settings.icon;
url = settings.click_url;
tag = "same";
}
// The notification properties
const notificationOptions = {
body: message,
icon: icon,
data: {
click_url: url
},
tag: tag
};
return self.registration.showNotification(title,
notificationOptions);
}
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
fb_messaging.setBackgroundMessageHandler(function(payload) {
console.log('Received background message ', payload);
// Customize notification here
if(payload.data && payload.data.M) {
var message = parseRealtimeMessage(payload.data.M);
return showNotification(message, payload.data.P);
}
});
// Forces a notification
self.addEventListener('message', function (evt) {
if(hasJsonStructure(evt.data)) {
var opts = JSON.parse(evt.data);
var message = opts.message;
evt.waitUntil(showNotification(message, opts));
}
else evt.waitUntil(showNotification(evt.data));
});
// The user has clicked on the notification ...
self.addEventListener('notificationclick', function(event) {
// Android doesn’t close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close();
if(event.notification.data && event.notification.data.click_url) {
// gets the notitication click url
var click_url = event.notification.data.click_url;
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({
type: "window"
}).then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == click_url && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
var url = click_url;
return clients.openWindow(url);
}
}));
}
});
function hasJsonStructure(str) {
if (typeof str !== 'string') return false;
try {
const result = JSON.parse(str);
const type = Object.prototype.toString.call(result);
return type === '[object Object]'
|| type === '[object Array]';
} catch (err) {
return false;
}
}
I had a similar problem. I was using the tag property from the options object. I gave a fixed value instead of a unique value. So only the first notification was showing up. Then I read this:
tag: An ID for a given notification that allows you to find, replace,
or remove the notification using a script if necessary.
in the documentation and understand cause it needs to be a unique value. So now every notification is showing up. How I see also your tag variable is hardcodated.

Call function if variable does not exist in a filter

I'm doing filtering on a data displayed in a view which is working correctly. I've placed a filter bar at the top of the screen where a user can filter the records. What I want to achieve is when the variable the user enters is not found in the records a function should be called
filterProducts(ev) {
this.productService.list = this.reOrderList;
const val = ev.target.value;
if (val && val.trim() !== '') {
this.productService.list = this.reOrderList.filter((item) => {
return (item.name.toLowerCase().indexOf(val.toLowerCase()) > -1);
});
} else {
// value doesn't exist console.log('call another function')
}
}
Check if any items are left in the array after the filter is complete:
if (this.productService.list.length) {
// The user's query was found in the array
} else {
// The user's query was not found in the array
}

Create a cell range output dynamically

In my add-in I am making an HTTP request and receiving an output. I want to place that output into a binding and have it expand the binding if necessary because the user won't necessarily know how many rows x columns the output will be. How would I go about doing this? Currently I am binding to a range, but if that range does not match the size of the [[]] that I am providing, then the data is not displayed in the sheet. So, this ends up requiring the user to know the size of the output.
What I'm doing currently using Angular is as follows (the problem with this being that the output isn't always the same size as the Office.BindingType.Matrix that the user selected in the spreadsheet):
I create the binding to where the output should be placed as follows:
inputBindFromPrompt(parameterId: number): Promise<IOfficeResult> {
let bindType: Office.BindingType;
if(this.inputBindings[parameterId].type != 'data.frame' && this.inputBindings[parameterId].type != 'vector') {
bindType = Office.BindingType.Text;
} else {
bindType = Office.BindingType.Matrix;
}
return new Promise((resolve, reject) => {
this.workbook.bindings.addFromPromptAsync(bindType, { id: this.inputBindings[parameterId].name },
(addBindingResult: Office.AsyncResult) => {
if(addBindingResult.status === Office.AsyncResultStatus.Failed) {
reject({
error: 'Unable to bind to workbook. Error: ' + addBindingResult.error.message
});
} else {
this.inputBindings[parameterId].binding = addBindingResult.value;
resolve({
success: 'Created binding ' + addBindingResult.value.type + ' on ' + addBindingResult.value.id
});
}
})
})
}
Then when the user submits via a button, the inputs are passed to a HTTP request service which then receives an output that I process into an array of arrays so that it can go into an Office.BindingType.Matrix:
this.isBusy = true;
this.feedback = 'submitted';
// Grab the values from the form
// Send as a POST and receive an output
// Put the output in the Excel sheet
this.webServicesService.postWebServices(this.service, this.inputParameters)
.subscribe(
(data: any) => {
// Correctly received data
// Access the data by name while looping through output parameters
this.error = false;
this.feedback = 'received data';
let i = 0;
this.outputParameters.forEach(element => {
// temporary name to identify the parameter
let name = element.name;
// Set the data value in the parameter
if(element.type == 'data.frame') {
let parameter = data[name];
this.feedback = parameter;
let excelData = [];
for(var key in parameter) {
if(parameter.hasOwnProperty(key)) {
var val = parameter[key];
excelData.push(val);
}
}
element.value = excelData;
}
else {
element.value = data[name];
}
// Set value in the form
let param = (<FormArray>this.serviceForm.controls['outputParameters']).at(i);
param.patchValue({
value: element.value
});
// Set value in the spreadsheet
this.excelService.outputSetText(i, element.value)
.then((result: IOfficeResult) => {
this.onResult(result);
i++;
});
}, (result: IOfficeResult) => {
this.onResult(result);
});
},
(error) => {
if(error.status == 400 || error.status == 401) {
// Return user to authentication page
this.authService.logout();
this.router.navigate(['/']);
} else {
// Tell user to try again
this.error = true;
}
}
);
The line above that is setting the value to the Office.Matrix.Binding is this.excelService.outputSetText(i, element.value), which calls this method in the Excel Service:
outputSetText(parameterId: number, data: any): Promise<IOfficeResult> {
return new Promise((resolve, reject) => {
if(this.outputBindings[parameterId].binding) {
this.outputBindings[parameterId].binding.setDataAsync(data, function (result: Office.AsyncResult) {
if(result.status == Office.AsyncResultStatus.Failed) {
reject({ error: 'Failed to set value. Error: ' + result.error.message });
} else {
let test: Office.Binding;
resolve({
success: 'successfully set value'
});
}
})
} else {
reject({
error: 'binding has not been created. bindFromPrompt must be called'
});
}
})
}
It's essentially using addFromPromptAsync() to set an output spot for the HTTP request. Then the user submits which sends the request, receives the data back and processes it into an array of arrays [[]] so that it can be the correct data format for Office.BindingType.Matrix. However, unless this is the same number of rows and columns as the binding originally selected, it won't display in the sheet. So, is there a binding type that will dynamically grow based on the data I give it? Or would I just need to release the current binding and make a new binding according to the size of the HTTP response data?
So long as you're using the "shared" (Office 2013) APIs, you will have this issue.
However, in the host-specific (2016+) APIs, you can easily solve the problem by resizing the range to suit your needs. Or more precisely, getting the binding, then asking for its range, then getting just the first (top-left) cell, and then resizing it:
await Excel.run(async (context) => {
let values = [
["", "Price"],
["Apple", 0.99],
["Orange", 1.59],
];
let firstCell = context.workbook.bindings.getItem("TestBinding").getRange().getCell(0, 0);
let fullRange = firstCell.getResizedRange(
values.length - 1, values[0].length - 1);
fullRange.values = values;
await context.sync();
});
You can try this snippet live in literally five clicks in the new Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/Zlatkovsky/5a2fc743bc9c8556d3eb3234e287d7f3. See more info about importing snippets to Script Lab.

Meteor subscribe callback running when subscription contains previous subscribe result

I am fairly new to meteor, and I am running into a strange issue with subscribe callbacks. I have a database containing courses and reviews. I'm using a publish/subscribe model on the reviews to return reviews that are only relevant to a selected class, and I want this to change each time a new class is clicked on. I want to print all the reviews and compile some metrics about the reviews (average quality, difficulty rating). Using the following code, with a subscribe that updates the reviews sent to the client, the printed reviews (which are grabbed from a helper) return correctly, but the metrics (which are grabbed on an onReady callback to the helper) are inaccurate. When the onReady function is run, the current result of the local reviews collection contains the union of the clicked class and the previously clicked class, even though the reviews themselves print correctly.
I've also tried using autoTracker, but I got the same results. Is there a way to clear previous subscribe results before updating them?
publish:
Meteor.publish('reviews', function validReviews(courseId, visiblity) {
console.log(courseId);
console.log(visiblity);
var ret = null
//show valid reviews for this course
if (courseId != undefined && courseId != "" && visiblity == 1) {
console.log("checked reviews for a class");
ret = Reviews.find({class : courseId, visible : 1}, {limit: 700});
} else if (courseId != undefined && courseId != "" && visiblity == 0) { //invalidated reviews for a class
console.log("unchecked reviews for a class");
ret = Reviews.find({class : courseId, visible : 0},
{limit: 700});
} else if (visiblity == 0) { //all invalidated reviews
console.log("all unchecked reviews");
ret = Reviews.find({visible : 0}, {limit: 700});
} else { //no reviews
console.log("no reviews");
//will always be empty because visible is 0 or 1. allows meteor to still send the ready
//flag when a new publication is sent
ret = Reviews.find({visible : 10});
}
//console.log(ret.fetch())
return ret
});
subscribe:
this.helpers({
reviews() {
return Reviews.find({});
}
});
and subscribe call, in constructor with the helpers:
constructor($scope) {
$scope.viewModel(this);
//when a new class is selected, update the reviews that are returned by the database and update the gauges
this.subscribe('reviews', () => [(this.getReactively('selectedClass'))._id, 1], {
//callback function, should only run once the reveiws collection updates, BUT ISNT
//seems to be combining the previously clicked class's reviews into the collection
onReady: function() {
console.log("class is: ", this.selectedClass);
if (this.isClassSelected == true) { //will later need to check that the side window is open
//create initial variables
var countGrade = 0;
var countDiff = 0;
var countQual = 0;
var count = 0;
//table to translate grades from numerical value
var gradeTranslation = ["C-", "C", "C+", "B-", "B", "B-", "A-", "A", "A+"];
//get all current reviews, which will now have only this class's reviews because of the subscribe.
var allReviews = Reviews.find({});
console.log(allReviews.fetch());
console.log("len is " + allReviews.fetch().length)
if (allReviews.fetch().length != 0) {
console.log("exist")
allReviews.forEach(function(review) {
count++;
countGrade = countGrade + Number(review["grade"]);
countDiff = countDiff + review["difficulty"];
countQual = countQual + review["quality"];
});
this.qual = (countQual/count).toFixed(1);
this.diff = (countDiff/count).toFixed(1);
this.grade = gradeTranslation[Math.floor(countGrade/count) - 1];
} else {
console.log("first else");
this.qual = 0;
this.diff = 0;
this.grade = "-";
}
} else {
console.log("second else");
this.qual = 0;
this.diff = 0;
this.grade = "-";
}
}
})
When using pub-sub the minimongo database on the client will contain the union of subscriptions unless they are explicitly cleared. For that reason you want to repeat the query that's in the publication on the client side so that you filter and sort the same way. Minimongo is very fast on the client and you typically have much less data there so don't worry about performance.
In your constructor you have:
var allReviews = Reviews.find({});
instead use:
var allReviews = Reviews.find(
{
class : (this.getReactively('selectedClass'))._id,
visible : 1
},
{limit: 700}
);
Another side tip: javascript is quite clever about truthy and falsy values.
if (courseId != undefined && courseId != "" && visibility == 1)
can be simplified to:
if (courseId && visibility)
assuming you're using visibility == 1 to denote true and visibility == 0 to denote false

ComboBox typeAhead works but valueField is null under certain behavioral conditions

Requesting a sanity check here please...
ExtJS 4.2 comboBox Typeahead works but having issues retrieving the valueField under the following conditions:-
1) If a user types a value and then instead of hitting enter or clicking on the select combo list, they click elsewhere then the valueField is empty but the selected value is present.
2) Assuming that the combobox item was selected correctly, If I enter an additional character and then backspace that character, the combo box can no longer find the valueField..its almost like it has reset itself.
Fiddle example
https://fiddle.sencha.com/#fiddle/je1
How to reproduce
If you enter Maggie in the combo box, you will see the valueField ID in the console window, if you append a character and then backspace the character, the ID in the console window is null
(You will need to open the console window to see the output)
forceSelection does not resolve this issue as I have a template and it will not accept an entry in the combobox that is not part of the store, and I need to use sumID for my valueField as I need to retrieve and pass that value to the server.
Thank you everyone, awesome to have such a great community!!
I was able to get around this by using forceSelection and overriding the setValue thus allowing template items not in the store but in the combo to be selected via forceSelection. From playing around with the combobox, IMO, for a good look and feel, forceSelection is the way to go.
Here is my override, refer to statement //start of override
This was a quick fix, I will refine statement when I am back in the office, below I am pasting the solution from memory, you get the idea.
setValue: function(value, doSelect) {
var me = this,
valueNotFoundText = me.valueNotFoundText,
inputEl = me.inputEl,
i, len, record,
dataObj,
matchedRecords = [],
displayTplData = [],
processedValue = [];
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
me.value = value;
me.setHiddenValue(me.value);
return me;
}
// This method processes multi-values, so ensure value is an array.
value = Ext.Array.from(value);
// Loop through values, matching each from the Store, and collecting matched records
for (i = 0, len = value.length; i < len; i++) {
record = value[i];
if (!record || !record.isModel) {
record = me.findRecordByValue(record);
}
// record found, select it.
if (record) {
matchedRecords.push(record);
displayTplData.push(record.data);
processedValue.push(record.get(me.valueField));
}
// record was not found, this could happen because
// store is not loaded or they set a value not in the store
else {
//start of override
// 'Select All Names' is the template item that was added // to the combo box, it looks like an entry from the store
// but it is not in the store
if (me.forceSelection && me.getDisplayValue() === 'Select All Names'){
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
}
//end of override
if (!me.forceSelection) {
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
// TODO: Add config to create new records on selection of a value that has no match in the Store
}
// Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
else if (Ext.isDefined(valueNotFoundText)) {
displayTplData.push(valueNotFoundText);
}
}
}
// Set the value of this field. If we are multiselecting, then that is an array.
me.setHiddenValue(processedValue);
me.value = me.multiSelect ? processedValue : processedValue[0];
if (!Ext.isDefined(me.value)) {
me.value = null;
}
me.displayTplData = displayTplData; //store for getDisplayValue method
me.lastSelection = me.valueModels = matchedRecords;
if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
inputEl.removeCls(me.emptyCls);
}
// Calculate raw value from the collection of Model data
me.setRawValue(me.getDisplayValue());
me.checkChange();
if (doSelect !== false) {
me.syncSelection();
}
me.applyEmptyText();
return me;
},
Look at the sources of Combobox and try override this method as follows
doLocalQuery: function(queryPlan) {
var me = this,
queryString = queryPlan.query;
if (!me.queryFilter) {
me.queryFilter = new Ext.util.Filter({
id: me.id + '-query-filter',
anyMatch: me.anyMatch,
caseSensitive: me.caseSensitive,
root: 'data',
property: me.displayField
});
me.store.addFilter(me.queryFilter, false);
}
if (queryString || !queryPlan.forceAll) {
me.queryFilter.disabled = false;
me.queryFilter.setValue(me.enableRegEx ? new RegExp(queryString) : queryString);
}
else {
me.queryFilter.disabled = true;
}
me.store.filter();
if (me.store.getCount()) {
if (me.rawValue === me.lastSelection[0].get(me.displayField)){
me.setValue(me.lastSelection);
} else {
if(me.store.getCount() === 1){
me.setValue(me.store.first());
}
me.expand();
}
} else {
me.collapse();
}
me.afterQuery(queryPlan);
},

Categories

Resources