Skip to content
Snippets Groups Projects
Commit 3c8987ff authored by Tom Robinson's avatar Tom Robinson
Browse files

Refactory Query functions from GuiQueryEditor into own file in preparation for...

Refactory Query functions from GuiQueryEditor into own file in preparation for Data Reference using them
parent a67937d1
No related branches found
No related tags found
No related merge requests found
......@@ -10,6 +10,8 @@ import RunButton from './run_button.react';
import SelectionModule from './selection_module.react';
import SortWidget from './sort_widget.react';
import Query from './query';
var cx = React.addons.classSet;
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
......@@ -32,7 +34,7 @@ export default React.createClass({
};
},
setQuery: function(dataset_query, notify) {
setQuery: function(dataset_query) {
this.props.notifyQueryModifiedFn(dataset_query);
},
......@@ -64,332 +66,93 @@ export default React.createClass({
query.database = this.props.query.database;
query.query.source_table = tableId;
this.setQuery(query, true);
this.setQuery(query);
},
canRun: function() {
if (this.hasValidAggregation()) {
return true;
}
return false;
return Query.canRun(this.props.query.query);
},
runQuery: function() {
var cleanQuery = this.cleanQuery(this.props.query);
this.props.runFn(cleanQuery);
},
cleanQuery: function(dataset_query) {
// it's possible the user left some half-done parts of the query on screen when they hit the run button, so find those
// things now and clear them out so that we have a nice clean set of valid clauses in our query
// TODO: breakouts
// filters
var queryFilters = this.getFilters();
if (queryFilters.length > 1) {
var hasNullValues = function(arr) {
for (var j=0; j < arr.length; j++) {
if (arr[j] === null) {
return true;
}
}
return false;
};
var cleanFilters = [queryFilters[0]];
for (var i=1; i < queryFilters.length; i++) {
if (!hasNullValues(queryFilters[i])) {
cleanFilters.push(queryFilters[i]);
}
}
if (cleanFilters.length > 1) {
dataset_query.query.filter = cleanFilters;
} else {
dataset_query.query.filter = [];
}
}
// TODO: limit
// TODO: sort
return dataset_query;
},
canAddDimensions: function() {
var MAX_DIMENSIONS = 2;
return (this.props.query.query.breakout.length < MAX_DIMENSIONS);
},
hasValidBreakout: function() {
return (this.props.query.query.breakout &&
this.props.query.query.breakout.length > 0 &&
this.props.query.query.breakout[0] !== null);
},
canSortByAggregateField: function() {
var SORTABLE_AGGREGATION_TYPES = new Set(["avg", "count", "distinct", "stddev", "sum"]);
return this.hasValidBreakout() && SORTABLE_AGGREGATION_TYPES.has(this.props.query.query.aggregation[0]);
Query.cleanQuery(this.props.query.query);
this.props.runFn(this.props.query);
},
addDimension: function() {
var query = this.props.query;
query.query.breakout.push(null);
this.setQuery(query, true);
Query.addDimension(this.props.query.query);
this.setQuery(this.props.query);
},
updateDimension: function(dimension, index) {
var query = this.props.query;
query.query.breakout[index] = dimension;
this.setQuery(query, true);
Query.updateDimension(this.props.query.query, dimension, index);
this.setQuery(this.props.query);
},
removeDimension: function(index) {
// TODO: when we remove breakouts we also need to remove any limits/sorts that don't make sense
var query = this.props.query;
query.query.breakout.splice(index, 1);
this.setQuery(query, true);
},
hasEmptyAggregation: function() {
var aggregation = this.props.query.query.aggregation;
if (aggregation !== undefined &&
aggregation.length > 0 &&
aggregation[0] !== null) {
return false;
}
return true;
},
hasValidAggregation: function() {
var aggregation = this.props.query.query.aggregation;
if (aggregation !== undefined &&
((aggregation.length === 1 && aggregation[0] !== null) ||
(aggregation.length === 2 && aggregation[0] !== null && aggregation[1] !== null))) {
return true;
}
return false;
},
isBareRowsAggregation: function() {
return (this.props.query.query.aggregation &&
this.props.query.query.aggregation.length > 0 &&
this.props.query.query.aggregation[0] === "rows");
Query.removeDimension(this.props.query.query, index);
this.setQuery(this.props.query);
},
updateAggregation: function(aggregationClause) {
var query = this.props.query;
query.query.aggregation = aggregationClause;
// for "rows" type aggregation we always clear out any dimensions because they don't make sense
if (aggregationClause.length > 0 && aggregationClause[0] === "rows") {
query.query.breakout = [];
}
this.setQuery(query, true);
},
renderAddIcon: function () {
return (
<span className="mr1">
<Icon name="add" width="12px" height="12px" />
</span>
)
},
getFilters: function() {
// Special handling for accessing query filters because it's been fairly complex to deal with their structure.
// This method provide a unified and consistent view of the filter definition for the rest of the tool to use.
var queryFilters = this.props.query.query.filter;
// quick check for older style filter definitions and tweak them to a format we want to work with
if (queryFilters && queryFilters.length > 0 && queryFilters[0] !== "AND") {
var reformattedFilters = [];
for (var i=0; i < queryFilters.length; i++) {
if (queryFilters[i] !== null) {
reformattedFilters = ["AND", queryFilters];
break;
}
}
queryFilters = reformattedFilters;
}
return queryFilters;
},
canAddFilter: function(queryFilters) {
var canAdd = true;
if (queryFilters && queryFilters.length > 0) {
var lastFilter = queryFilters[queryFilters.length - 1];
// simply make sure that there are no null values in the last filter
for (var i=0; i < lastFilter.length; i++) {
if (lastFilter[i] === null) {
canAdd = false;
}
}
} else {
canAdd = false;
}
return canAdd;
Query.updateAggregation(this.props.query.query, aggregationClause);
this.setQuery(this.props.query);
},
addFilter: function() {
var query = this.props.query,
queryFilters = this.getFilters();
if (queryFilters.length === 0) {
queryFilters = ["AND", [null, null, null]];
} else {
queryFilters.push([null, null, null]);
}
query.query.filter = queryFilters;
this.setQuery(query, true);
Query.addFilter(this.props.query.query);
this.setQuery(this.props.query);
},
updateFilter: function(index, filter) {
var query = this.props.query,
queryFilters = this.getFilters();
queryFilters[index] = filter;
query.query.filter = queryFilters;
this.setQuery(query, true);
Query.updateFilter(this.props.query.query, index, filter);
this.setQuery(this.props.query);
},
removeFilter: function(index) {
var query = this.props.query,
queryFilters = this.getFilters();
if (queryFilters.length === 2) {
// this equates to having a single filter because the arry looks like ... ["AND" [a filter def array]]
queryFilters = [];
} else {
queryFilters.splice(index, 1);
}
query.query.filter = queryFilters;
this.setQuery(query, true);
},
canAddLimitAndSort: function() {
// limits and sorts only make sense if we know there will be multiple rows
var query = this.props.query;
if (this.isBareRowsAggregation()) {
return true;
} else if (this.hasValidBreakout()) {
return true;
} else {
return false;
}
},
getSortableFields: function() {
// in bare rows all fields are sortable, otherwise we only sort by our breakout columns
var query = this.props.query;
// start with all fields
var fieldList = [];
for(var key in this.props.options.fields_lookup) {
fieldList.push(this.props.options.fields_lookup[key]);
}
if (this.isBareRowsAggregation()) {
return fieldList;
} else if (this.hasValidBreakout()) {
// further filter field list down to only fields in our breakout clause
var breakoutFieldList = [];
this.props.query.query.breakout.map(function (breakoutFieldId) {
for (var idx in fieldList) {
if (fieldList[idx].id === breakoutFieldId) {
breakoutFieldList.push(fieldList[idx]);
}
}
}.bind(this));
if (this.canSortByAggregateField()) {
breakoutFieldList.push({
id: ["aggregation", 0],
name: this.props.query.query.aggregation[0] // e.g. "sum"
});
}
return breakoutFieldList;
} else {
return [];
}
Query.removeFilter(this.props.query.query, index);
this.setQuery(this.props.query);
},
addLimit: function() {
var query = this.props.query;
query.query.limit = null;
this.setQuery(query, true);
Query.addLimit(this.props.query.query);
this.setQuery(this.props.query);
},
updateLimit: function(limit) {
var query = this.props.query;
query.query.limit = limit;
this.setQuery(query, true);
Query.updateLimit(this.props.query.query, limit);
this.setQuery(this.props.query);
},
removeLimit: function() {
var query = this.props.query;
delete query.query.limit;
this.setQuery(query, true);
},
canAddSort: function() {
// TODO: allow for multiple sorting choices
return false;
Query.removeLimit(this.props.query.query);
this.setQuery(this.props.query);
},
addSort: function() {
// TODO: make sure people don't try to sort by the same field multiple times
var query = this.props.query,
order_by = query.query.order_by;
if (!order_by) {
order_by = [];
}
order_by.push([null, "ascending"]);
query.query.order_by = order_by;
this.setQuery(query, true);
Query.addSort(this.props.query.query);
this.setQuery(this.props.query);
},
updateSort: function(index, sort) {
var query = this.props.query;
query.query.order_by[index] = sort;
this.setQuery(query, true);
Query.updateSort(this.props.query.query, index, sort);
this.setQuery(this.props.query);
},
removeSort: function(index) {
var query = this.props.query,
queryOrderBy = query.query.order_by;
Query.removeSort(this.props.query.query, index);
this.setQuery(this.props.query);
},
if (queryOrderBy.length === 1) {
delete query.query.order_by;
} else {
queryOrderBy.splice(index, 1);
}
getSortableFields: function() {
return Query.getSortableFields(this.props.query.query, this.props.options.fields_lookup);
},
this.setQuery(query, true);
renderAddIcon: function () {
return (
<span className="mr1">
<Icon name="add" width="12px" height="12px" />
</span>
)
},
renderDbSelector: function() {
......@@ -439,7 +202,7 @@ export default React.createClass({
renderFilterButton: function() {
if (this.props.query.query.source_table &&
this.getFilters().length === 0 &&
Query.getFilters(this.props.query.query).length === 0 &&
this.props.options &&
this.props.options.fields.length > 0) {
return (
......@@ -455,7 +218,7 @@ export default React.createClass({
// breakout clause. must have table details available & a valid aggregation defined
if (this.props.options &&
this.props.options.breakout_options.fields.length > 0 &&
!this.hasEmptyAggregation()) {
!Query.hasEmptyAggregation(this.props.query.query)) {
// only render a label for our breakout if we have a valid breakout clause already
var breakoutLabel;
......@@ -536,7 +299,7 @@ export default React.createClass({
},
renderFilterSelector: function() {
var queryFilters = this.getFilters();
var queryFilters = Query.getFilters(this.props.query.query);
if (this.props.options && queryFilters && queryFilters.length > 0) {
var component = this;
......@@ -563,7 +326,7 @@ export default React.createClass({
// TODO: proper check for isFilterComplete(filter)
var addFilterButton;
if (this.canAddFilter(queryFilters)) {
if (Query.canAddFilter(this.props.query.query)) {
addFilterButton = (
<a className="QueryOption p1 lg-p2" onClick={this.addFilter}>
{this.renderAddIcon()}
......@@ -586,7 +349,7 @@ export default React.createClass({
},
renderLimitAndSort: function() {
if (this.props.options && !this.hasEmptyAggregation() &&
if (this.props.options && !Query.hasEmptyAggregation(this.props.query.query) &&
(this.props.query.query.limit !== undefined || this.props.query.query.order_by !== undefined)) {
var limitSection;
......@@ -640,7 +403,7 @@ export default React.createClass({
);
} else {
var addSortButton;
if (this.canAddSort()) {
if (Query.canAddSort(this.props.query.query)) {
addSortButton = (
<a onClick={this.addSort}>Add another sort</a>
);
......@@ -665,7 +428,7 @@ export default React.createClass({
</div>
);
} else if (this.canAddLimitAndSort()) {
} else if (Query.canAddLimitAndSort(this.props.query.query)) {
return (
<div className={this.props.querySectionClasses}>
<a className="QueryOption QueryOption--offset p1 lg-p2" onClick={this.addLimit}>
......
'use strict';
var Query = {
canRun: function(query) {
if (Query.hasValidAggregation(query)) {
return true;
}
return false;
},
cleanQuery: function(query) {
// it's possible the user left some half-done parts of the query on screen when they hit the run button, so find those
// things now and clear them out so that we have a nice clean set of valid clauses in our query
// TODO: breakouts
// filters
var queryFilters = Query.getFilters(query);
if (queryFilters.length > 1) {
var hasNullValues = function(arr) {
for (var j=0; j < arr.length; j++) {
if (arr[j] === null) {
return true;
}
}
return false;
};
var cleanFilters = [queryFilters[0]];
for (var i=1; i < queryFilters.length; i++) {
if (!hasNullValues(queryFilters[i])) {
cleanFilters.push(queryFilters[i]);
}
}
if (cleanFilters.length > 1) {
query.filter = cleanFilters;
} else {
query.filter = [];
}
}
// TODO: limit
// TODO: sort
return query;
},
canAddDimensions: function(query) {
var MAX_DIMENSIONS = 2;
return (query.breakout.length < MAX_DIMENSIONS);
},
hasValidBreakout: function(query) {
return (query.breakout &&
query.breakout.length > 0 &&
query.breakout[0] !== null);
},
canSortByAggregateField: function(query) {
var SORTABLE_AGGREGATION_TYPES = new Set(["avg", "count", "distinct", "stddev", "sum"]);
return Query.hasValidBreakout(query) && SORTABLE_AGGREGATION_TYPES.has(query.aggregation[0]);
},
addDimension: function(query) {
query.breakout.push(null);
},
updateDimension: function(query, dimension, index) {
query.breakout[index] = dimension;
},
removeDimension: function(query, index) {
// TODO: when we remove breakouts we also need to remove any limits/sorts that don't make sense
query.breakout.splice(index, 1);
},
hasEmptyAggregation: function(query) {
var aggregation = query.aggregation;
if (aggregation !== undefined &&
aggregation.length > 0 &&
aggregation[0] !== null) {
return false;
}
return true;
},
hasValidAggregation: function(query) {
var aggregation = query.aggregation;
if (aggregation !== undefined &&
((aggregation.length === 1 && aggregation[0] !== null) ||
(aggregation.length === 2 && aggregation[0] !== null && aggregation[1] !== null))) {
return true;
}
return false;
},
isBareRowsAggregation: function(query) {
return (query.aggregation &&
query.aggregation.length > 0 &&
query.aggregation[0] === "rows");
},
updateAggregation: function(query, aggregationClause) {
query.aggregation = aggregationClause;
// for "rows" type aggregation we always clear out any dimensions because they don't make sense
if (aggregationClause.length > 0 && aggregationClause[0] === "rows") {
query.breakout = [];
}
},
getFilters: function(query) {
// Special handling for accessing query filters because it's been fairly complex to deal with their structure.
// This method provide a unified and consistent view of the filter definition for the rest of the tool to use.
var queryFilters = query.filter;
// quick check for older style filter definitions and tweak them to a format we want to work with
if (queryFilters && queryFilters.length > 0 && queryFilters[0] !== "AND") {
var reformattedFilters = [];
for (var i=0; i < queryFilters.length; i++) {
if (queryFilters[i] !== null) {
reformattedFilters = ["AND", queryFilters];
break;
}
}
queryFilters = reformattedFilters;
}
return queryFilters;
},
canAddFilter: function(query) {
var canAdd = true;
var queryFilters = Query.getFilters(query);
if (queryFilters && queryFilters.length > 0) {
var lastFilter = queryFilters[queryFilters.length - 1];
// simply make sure that there are no null values in the last filter
for (var i=0; i < lastFilter.length; i++) {
if (lastFilter[i] === null) {
canAdd = false;
}
}
} else {
canAdd = false;
}
return canAdd;
},
addFilter: function(query) {
var queryFilters = Query.getFilters(query);
if (queryFilters.length === 0) {
queryFilters = ["AND", [null, null, null]];
} else {
queryFilters.push([null, null, null]);
}
query.filter = queryFilters;
},
updateFilter: function(query, index, filter) {
var queryFilters = Query.getFilters(query);
queryFilters[index] = filter;
query.filter = queryFilters;
},
removeFilter: function(query, index) {
var queryFilters = Query.getFilters(query);
if (queryFilters.length === 2) {
// this equates to having a single filter because the arry looks like ... ["AND" [a filter def array]]
queryFilters = [];
} else {
queryFilters.splice(index, 1);
}
query.filter = queryFilters;
},
canAddLimitAndSort: function(query) {
if (Query.isBareRowsAggregation(query)) {
return true;
} else if (Query.hasValidBreakout(query)) {
return true;
} else {
return false;
}
},
getSortableFields: function(query, fields) {
// in bare rows all fields are sortable, otherwise we only sort by our breakout columns
// start with all fields
var fieldList = [];
for(var key in fields) {
fieldList.push(fields[key]);
}
if (Query.isBareRowsAggregation(query)) {
return fieldList;
} else if (Query.hasValidBreakout(query)) {
// further filter field list down to only fields in our breakout clause
var breakoutFieldList = [];
query.breakout.map(function (breakoutFieldId) {
for (var idx in fieldList) {
if (fieldList[idx].id === breakoutFieldId) {
breakoutFieldList.push(fieldList[idx]);
}
}
});
if (Query.canSortByAggregateField(query)) {
breakoutFieldList.push({
id: ["aggregation", 0],
name: query.aggregation[0] // e.g. "sum"
});
}
return breakoutFieldList;
} else {
return [];
}
},
addLimit: function(query) {
query.limit = null;
},
updateLimit: function(query, limit) {
query.limit = limit;
},
removeLimit: function(query) {
delete query.limit;
},
canAddSort: function(query) {
// TODO: allow for multiple sorting choices
return false;
},
addSort: function(query) {
// TODO: make sure people don't try to sort by the same field multiple times
var order_by = query.order_by;
if (!order_by) {
order_by = [];
}
order_by.push([null, "ascending"]);
query.order_by = order_by;
},
updateSort: function(query, index, sort) {
query.order_by[index] = sort;
},
removeSort: function(query, index) {
var queryOrderBy = query.order_by;
if (queryOrderBy.length === 1) {
delete query.order_by;
} else {
queryOrderBy.splice(index, 1);
}
}
}
export default Query;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment