mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-03-13 05:15:30 +00:00
799 lines
28 KiB
TypeScript
799 lines
28 KiB
TypeScript
import * as ko from "knockout";
|
|
import { KeyCodes } from "../../../Common/Constants";
|
|
import { userContext } from "../../../UserContext";
|
|
import * as Constants from "../Constants";
|
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
|
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
|
import * as TableEntityProcessor from "../TableEntityProcessor";
|
|
import * as Utilities from "../Utilities";
|
|
import ClauseGroup from "./ClauseGroup";
|
|
import ClauseGroupViewModel from "./ClauseGroupViewModel";
|
|
import * as CustomTimestampHelper from "./CustomTimestampHelper";
|
|
import * as DateTimeUtilities from "./DateTimeUtilities";
|
|
import QueryClauseViewModel from "./QueryClauseViewModel";
|
|
import QueryViewModel from "./QueryViewModel";
|
|
|
|
export default class QueryBuilderViewModel {
|
|
/* Labels */
|
|
public andLabel = "And/Or"; // localize
|
|
public actionLabel = "Action"; // localize
|
|
public fieldLabel = "Field"; // localize
|
|
public dataTypeLabel = "Type"; // localize
|
|
public operatorLabel = "Operator"; // localize
|
|
public valueLabel = "Value"; // localize
|
|
|
|
/* controls */
|
|
public addNewClauseLine = "Add new clause"; // localize
|
|
public insertNewFilterLine = "Insert new filter line"; // localize
|
|
public removeThisFilterLine = "Remove this filter line"; // localize
|
|
public groupSelectedClauses = "Group selected clauses"; // localize
|
|
public clauseArray = ko.observableArray<QueryClauseViewModel>(); // This is for storing the clauses in flattened form queryClauses for easier UI data binding.
|
|
public queryClauses = new ClauseGroup(true, null); // The actual data structure containing the clause information.
|
|
public columnOptions: ko.ObservableArray<string>;
|
|
public canGroupClauses = ko.observable<boolean>(false);
|
|
|
|
/* Observables */
|
|
public edmTypes = ko.observableArray([
|
|
Constants.TableType.String,
|
|
Constants.TableType.Boolean,
|
|
Constants.TableType.Binary,
|
|
Constants.TableType.DateTime,
|
|
Constants.TableType.Double,
|
|
Constants.TableType.Guid,
|
|
Constants.TableType.Int32,
|
|
Constants.TableType.Int64,
|
|
"",
|
|
]);
|
|
public operators = ko.observableArray([
|
|
Constants.Operator.Equal,
|
|
Constants.Operator.GreaterThan,
|
|
Constants.Operator.GreaterThanOrEqualTo,
|
|
Constants.Operator.LessThan,
|
|
Constants.Operator.LessThanOrEqualTo,
|
|
Constants.Operator.NotEqualTo,
|
|
"",
|
|
]);
|
|
public clauseRules = ko.observableArray([Constants.ClauseRule.And, Constants.ClauseRule.Or]);
|
|
public timeOptions = ko.observableArray([
|
|
Constants.timeOptions.lastHour,
|
|
Constants.timeOptions.last24Hours,
|
|
Constants.timeOptions.last7Days,
|
|
Constants.timeOptions.last31Days,
|
|
Constants.timeOptions.last365Days,
|
|
Constants.timeOptions.currentMonth,
|
|
Constants.timeOptions.currentYear,
|
|
//Constants.timeOptions.custom
|
|
]);
|
|
public queryString = ko.observable<string>();
|
|
private _queryViewModel: QueryViewModel;
|
|
public tableEntityListViewModel: TableEntityListViewModel;
|
|
private scrollEventListener: boolean;
|
|
|
|
constructor(queryViewModel: QueryViewModel, tableEntityListViewModel: TableEntityListViewModel) {
|
|
if (userContext.apiType === "Cassandra") {
|
|
this.edmTypes([
|
|
Constants.CassandraType.Text,
|
|
Constants.CassandraType.Ascii,
|
|
Constants.CassandraType.Bigint,
|
|
Constants.CassandraType.Blob,
|
|
Constants.CassandraType.Boolean,
|
|
Constants.CassandraType.Decimal,
|
|
Constants.CassandraType.Double,
|
|
Constants.CassandraType.Float,
|
|
Constants.CassandraType.Int,
|
|
Constants.CassandraType.Uuid,
|
|
Constants.CassandraType.Varchar,
|
|
Constants.CassandraType.Varint,
|
|
Constants.CassandraType.Inet,
|
|
Constants.CassandraType.Smallint,
|
|
Constants.CassandraType.Tinyint,
|
|
]);
|
|
this.clauseRules([
|
|
Constants.ClauseRule.And,
|
|
// OR is not supported in CQL
|
|
]);
|
|
this.andLabel = "And";
|
|
}
|
|
this.clauseArray();
|
|
|
|
this._queryViewModel = queryViewModel;
|
|
this.tableEntityListViewModel = tableEntityListViewModel;
|
|
this.columnOptions = ko.observableArray<string>(queryViewModel.columnOptions());
|
|
|
|
this.columnOptions.subscribe((newColumnOptions) => {
|
|
queryViewModel.columnOptions(newColumnOptions);
|
|
});
|
|
}
|
|
|
|
public setExample() {
|
|
var example1 = new QueryClauseViewModel(
|
|
this,
|
|
"",
|
|
"PartitionKey",
|
|
this.edmTypes()[0],
|
|
Constants.Operator.Equal,
|
|
this.tableEntityListViewModel.items()[0].PartitionKey._,
|
|
false,
|
|
"",
|
|
"",
|
|
"",
|
|
//null,
|
|
true
|
|
);
|
|
var example2 = new QueryClauseViewModel(
|
|
this,
|
|
"And",
|
|
"RowKey",
|
|
this.edmTypes()[0],
|
|
Constants.Operator.Equal,
|
|
this.tableEntityListViewModel.items()[0].RowKey._,
|
|
true,
|
|
"",
|
|
"",
|
|
"",
|
|
//null,
|
|
true
|
|
);
|
|
this.addClauseImpl(example1, 0);
|
|
this.addClauseImpl(example2, 1);
|
|
}
|
|
|
|
public getODataFilterFromClauses = (): string => {
|
|
var filterString: string = "";
|
|
var treeTraversal = (group: ClauseGroup): void => {
|
|
for (var i = 0; i < group.children.length; i++) {
|
|
var currentItem = group.children[i];
|
|
|
|
if (currentItem instanceof QueryClauseViewModel) {
|
|
var clause = <QueryClauseViewModel>currentItem;
|
|
this.timestampToValue(clause);
|
|
filterString = filterString.concat(
|
|
this.constructODataClause(
|
|
filterString === "" ? "" : clause.and_or(),
|
|
this.generateLeftParentheses(clause),
|
|
clause.field(),
|
|
clause.type(),
|
|
clause.operator(),
|
|
clause.value(),
|
|
this.generateRightParentheses(clause)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (currentItem instanceof ClauseGroup) {
|
|
treeTraversal(<ClauseGroup>currentItem);
|
|
}
|
|
}
|
|
};
|
|
|
|
treeTraversal(this.queryClauses);
|
|
|
|
return filterString.trim();
|
|
};
|
|
|
|
public getSqlFilterFromClauses = (): string => {
|
|
var filterString: string = "SELECT * FROM c";
|
|
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
|
filterString = "SELECT";
|
|
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
|
|
selectText &&
|
|
selectText.forEach((value: string) => {
|
|
if (value === Constants.EntityKeyNames.PartitionKey) {
|
|
value = `["${TableEntityProcessor.keyProperties.PartitionKey}"]`;
|
|
filterString = filterString.concat(filterString === "SELECT" ? " c" : ", c");
|
|
} else if (value === Constants.EntityKeyNames.RowKey) {
|
|
value = `["${TableEntityProcessor.keyProperties.Id}"]`;
|
|
filterString = filterString.concat(filterString === "SELECT" ? " c" : ", c");
|
|
} else {
|
|
if (value === Constants.EntityKeyNames.Timestamp) {
|
|
value = TableEntityProcessor.keyProperties.Timestamp;
|
|
}
|
|
filterString = filterString.concat(filterString === "SELECT" ? " c." : ", c.");
|
|
}
|
|
filterString = filterString.concat(value);
|
|
});
|
|
filterString = filterString.concat(" FROM c");
|
|
}
|
|
if (this.queryClauses.children.length === 0) {
|
|
return filterString;
|
|
}
|
|
filterString = filterString.concat(" WHERE");
|
|
var first = true;
|
|
var treeTraversal = (group: ClauseGroup): void => {
|
|
for (var i = 0; i < group.children.length; i++) {
|
|
var currentItem = group.children[i];
|
|
|
|
if (currentItem instanceof QueryClauseViewModel) {
|
|
var clause = <QueryClauseViewModel>currentItem;
|
|
let timeStampValue: string = this.timestampToSqlValue(clause);
|
|
var value = clause.value();
|
|
if (!clause.isValue()) {
|
|
value = timeStampValue;
|
|
}
|
|
filterString = filterString.concat(
|
|
this.constructSqlClause(
|
|
first ? "" : clause.and_or(),
|
|
this.generateLeftParentheses(clause),
|
|
clause.field(),
|
|
clause.type(),
|
|
clause.operator(),
|
|
value,
|
|
this.generateRightParentheses(clause)
|
|
)
|
|
);
|
|
first = false;
|
|
}
|
|
|
|
if (currentItem instanceof ClauseGroup) {
|
|
treeTraversal(<ClauseGroup>currentItem);
|
|
}
|
|
}
|
|
};
|
|
|
|
treeTraversal(this.queryClauses);
|
|
|
|
return filterString.trim();
|
|
};
|
|
|
|
public getCqlFilterFromClauses = (): string => {
|
|
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
|
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
|
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
|
var filterString: string = `SELECT * FROM ${tableToQuery}`;
|
|
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
|
filterString = "SELECT";
|
|
const selectText = this._queryViewModel && this._queryViewModel.selectText && this._queryViewModel.selectText();
|
|
selectText &&
|
|
selectText.forEach((value: string) => {
|
|
filterString = filterString.concat(filterString === "SELECT" ? " " : ", ");
|
|
filterString = filterString.concat(value);
|
|
});
|
|
filterString = filterString.concat(` FROM ${tableToQuery}`);
|
|
}
|
|
if (this.queryClauses.children.length === 0) {
|
|
return filterString;
|
|
}
|
|
filterString = filterString.concat(" WHERE");
|
|
var first = true;
|
|
var treeTraversal = (group: ClauseGroup): void => {
|
|
for (var i = 0; i < group.children.length; i++) {
|
|
var currentItem = group.children[i];
|
|
|
|
if (currentItem instanceof QueryClauseViewModel) {
|
|
var clause = <QueryClauseViewModel>currentItem;
|
|
let timeStampValue: string = this.timestampToSqlValue(clause);
|
|
var value = clause.value();
|
|
if (!clause.isValue()) {
|
|
value = timeStampValue;
|
|
}
|
|
filterString = filterString.concat(
|
|
this.constructCqlClause(
|
|
first ? "" : clause.and_or(),
|
|
this.generateLeftParentheses(clause),
|
|
clause.field(),
|
|
clause.type(),
|
|
clause.operator(),
|
|
value,
|
|
this.generateRightParentheses(clause)
|
|
)
|
|
);
|
|
first = false;
|
|
}
|
|
|
|
if (currentItem instanceof ClauseGroup) {
|
|
treeTraversal(<ClauseGroup>currentItem);
|
|
}
|
|
}
|
|
};
|
|
|
|
treeTraversal(this.queryClauses);
|
|
|
|
return filterString.trim();
|
|
};
|
|
|
|
public updateColumnOptions = (): void => {
|
|
let originalHeaders = this.columnOptions();
|
|
let newHeaders = this.tableEntityListViewModel.headers;
|
|
this.columnOptions(newHeaders.sort(DataTableUtilities.compareTableColumns));
|
|
};
|
|
|
|
private generateLeftParentheses(clause: QueryClauseViewModel): string {
|
|
var result = "";
|
|
|
|
if (clause.clauseGroup.isRootGroup || clause.clauseGroup.children.indexOf(clause) !== 0) {
|
|
return result;
|
|
} else {
|
|
result = result.concat("(");
|
|
}
|
|
|
|
var currentGroup: ClauseGroup = clause.clauseGroup;
|
|
|
|
while (
|
|
!currentGroup.isRootGroup &&
|
|
!currentGroup.parentGroup.isRootGroup &&
|
|
currentGroup.parentGroup.children.indexOf(currentGroup) === 0
|
|
) {
|
|
result = result.concat("(");
|
|
currentGroup = currentGroup.parentGroup;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private generateRightParentheses(clause: QueryClauseViewModel): string {
|
|
var result = "";
|
|
|
|
if (
|
|
clause.clauseGroup.isRootGroup ||
|
|
clause.clauseGroup.children.indexOf(clause) !== clause.clauseGroup.children.length - 1
|
|
) {
|
|
return result;
|
|
} else {
|
|
result = result.concat(")");
|
|
}
|
|
|
|
var currentGroup: ClauseGroup = clause.clauseGroup;
|
|
|
|
while (
|
|
!currentGroup.isRootGroup &&
|
|
!currentGroup.parentGroup.isRootGroup &&
|
|
currentGroup.parentGroup.children.indexOf(currentGroup) === currentGroup.parentGroup.children.length - 1
|
|
) {
|
|
result = result.concat(")");
|
|
currentGroup = currentGroup.parentGroup;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private constructODataClause = (
|
|
clauseRule: string,
|
|
leftParentheses: string,
|
|
propertyName: string,
|
|
type: string,
|
|
operator: string,
|
|
value: string,
|
|
rightParentheses: string
|
|
): string => {
|
|
switch (type) {
|
|
case Constants.TableType.DateTime:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
|
operator
|
|
)} ${value}${rightParentheses}`;
|
|
case Constants.TableType.String:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
|
operator
|
|
)} \'${value}\'${rightParentheses}`;
|
|
case Constants.TableType.Guid:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
|
operator
|
|
)} guid\'${value}\'${rightParentheses}`;
|
|
case Constants.TableType.Binary:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
|
operator
|
|
)} binary\'${value}\'${rightParentheses}`;
|
|
default:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}${propertyName} ${this.operatorConverter(
|
|
operator
|
|
)} ${value}${rightParentheses}`;
|
|
}
|
|
};
|
|
|
|
private constructSqlClause = (
|
|
clauseRule: string,
|
|
leftParentheses: string,
|
|
propertyName: string,
|
|
type: string,
|
|
operator: string,
|
|
value: string,
|
|
rightParentheses: string
|
|
): string => {
|
|
if (propertyName === Constants.EntityKeyNames.PartitionKey) {
|
|
propertyName = TableEntityProcessor.keyProperties.PartitionKey;
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c["${propertyName}"] ${operator} \'${value}\'${rightParentheses}`;
|
|
} else if (propertyName === Constants.EntityKeyNames.RowKey) {
|
|
propertyName = TableEntityProcessor.keyProperties.Id;
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} \'${value}\'${rightParentheses}`;
|
|
} else if (propertyName === Constants.EntityKeyNames.Timestamp) {
|
|
propertyName = TableEntityProcessor.keyProperties.Timestamp;
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName} ${operator} ${DateTimeUtilities.convertJSDateToUnix(
|
|
value
|
|
)}${rightParentheses}`;
|
|
}
|
|
switch (type) {
|
|
case Constants.TableType.DateTime:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${DateTimeUtilities.convertJSDateToTicksWithPadding(
|
|
value
|
|
)}\'${rightParentheses}`;
|
|
case Constants.TableType.Int64:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${Utilities.padLongWithZeros(
|
|
value
|
|
)}\'${rightParentheses}`;
|
|
case Constants.TableType.String:
|
|
case Constants.TableType.Guid:
|
|
case Constants.TableType.Binary:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} \'${value}\'${rightParentheses}`;
|
|
default:
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses}c.${propertyName}["$v"] ${operator} ${value}${rightParentheses}`;
|
|
}
|
|
};
|
|
|
|
private constructCqlClause = (
|
|
clauseRule: string,
|
|
leftParentheses: string,
|
|
propertyName: string,
|
|
type: string,
|
|
operator: string,
|
|
value: string,
|
|
rightParentheses: string
|
|
): string => {
|
|
if (
|
|
type === Constants.CassandraType.Text ||
|
|
type === Constants.CassandraType.Inet ||
|
|
type === Constants.CassandraType.Ascii ||
|
|
type === Constants.CassandraType.Varchar
|
|
) {
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} \'${value}\'${rightParentheses}`;
|
|
}
|
|
return ` ${clauseRule.toLowerCase()} ${leftParentheses} ${propertyName} ${operator} ${value}${rightParentheses}`;
|
|
};
|
|
|
|
private operatorConverter = (operator: string): string => {
|
|
switch (operator) {
|
|
case Constants.Operator.Equal:
|
|
return Constants.ODataOperator.EqualTo;
|
|
case Constants.Operator.GreaterThan:
|
|
return Constants.ODataOperator.GreaterThan;
|
|
case Constants.Operator.GreaterThanOrEqualTo:
|
|
return Constants.ODataOperator.GreaterThanOrEqualTo;
|
|
case Constants.Operator.LessThan:
|
|
return Constants.ODataOperator.LessThan;
|
|
case Constants.Operator.LessThanOrEqualTo:
|
|
return Constants.ODataOperator.LessThanOrEqualTo;
|
|
case Constants.Operator.NotEqualTo:
|
|
return Constants.ODataOperator.NotEqualTo;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
public groupClauses = (): void => {
|
|
this.queryClauses.groupSelectedItems();
|
|
this.updateClauseArray();
|
|
this.updateCanGroupClauses();
|
|
};
|
|
|
|
public addClauseIndex = (index: number, data: any): void => {
|
|
if (index < 0) {
|
|
index = 0;
|
|
}
|
|
var newClause = new QueryClauseViewModel(
|
|
this,
|
|
"And",
|
|
"",
|
|
this.edmTypes()[0],
|
|
Constants.Operator.EqualTo,
|
|
"",
|
|
true,
|
|
"",
|
|
"",
|
|
"",
|
|
//null,
|
|
true
|
|
);
|
|
this.addClauseImpl(newClause, index);
|
|
if (index === this.clauseArray().length - 1) {
|
|
this.scrollToBottom();
|
|
}
|
|
this.updateCanGroupClauses();
|
|
newClause.isAndOrFocused(true);
|
|
$(window).resize();
|
|
};
|
|
|
|
// adds a new clause to the end of the array
|
|
public addNewClause = (): void => {
|
|
this.addClauseIndex(this.clauseArray().length, null);
|
|
};
|
|
|
|
public onAddClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => {
|
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
this.addClauseIndex(index, data);
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
public onAddNewClauseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
this.addClauseIndex(this.clauseArray().length - 1, null);
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
public deleteClause = (index: number, data: any): void => {
|
|
this.deleteClauseImpl(index);
|
|
if (this.clauseArray().length !== 0) {
|
|
this.clauseArray()[0].and_or("");
|
|
this.clauseArray()[0].canAnd(false);
|
|
}
|
|
this.updateCanGroupClauses();
|
|
$(window).resize();
|
|
};
|
|
|
|
public onDeleteClauseKeyDown = (index: number, data: any, event: KeyboardEvent, source: any): boolean => {
|
|
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
this.deleteClause(index, data);
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Generates an array of ClauseGroupViewModel objects for UI to display group information for this clause.
|
|
* All clauses have the same number of ClauseGroupViewModel objects, which is the depth of the clause tree.
|
|
* If the current clause is not the deepest in the tree, then the array will be filled by either a placeholder
|
|
* (transparent) or its parent group view models.
|
|
*/
|
|
public getClauseGroupViewModels = (clause: QueryClauseViewModel): ClauseGroupViewModel[] => {
|
|
var placeHolderGroupViewModel = new ClauseGroupViewModel(this.queryClauses, false, this);
|
|
var treeDepth = this.queryClauses.getTreeDepth();
|
|
var groupViewModels = new Array<ClauseGroupViewModel>(treeDepth);
|
|
|
|
// Prefill the arry with placeholders.
|
|
for (var i = 0; i < groupViewModels.length; i++) {
|
|
groupViewModels[i] = placeHolderGroupViewModel;
|
|
}
|
|
|
|
var currentGroup = clause.clauseGroup;
|
|
|
|
// This function determines whether the path from clause to the current group is on the left most.
|
|
var isLeftMostPath = (): boolean => {
|
|
var group = clause.clauseGroup;
|
|
|
|
if (group.children.indexOf(clause) !== 0) {
|
|
return false;
|
|
}
|
|
|
|
while (true) {
|
|
if (group.getId() === currentGroup.getId()) {
|
|
break;
|
|
}
|
|
|
|
if (group.parentGroup.children.indexOf(group) !== 0) {
|
|
return false;
|
|
}
|
|
|
|
group = group.parentGroup;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// This function determines whether the path from clause to the current group is on the right most.
|
|
var isRightMostPath = (): boolean => {
|
|
var group = clause.clauseGroup;
|
|
|
|
if (group.children.indexOf(clause) !== group.children.length - 1) {
|
|
return false;
|
|
}
|
|
|
|
while (true) {
|
|
if (group.getId() === currentGroup.getId()) {
|
|
break;
|
|
}
|
|
|
|
if (group.parentGroup.children.indexOf(group) !== group.parentGroup.children.length - 1) {
|
|
return false;
|
|
}
|
|
|
|
group = group.parentGroup;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var vmIndex = groupViewModels.length - 1;
|
|
var skipIndex = -1;
|
|
var lastDepth = clause.groupDepth;
|
|
|
|
while (!currentGroup.isRootGroup) {
|
|
// The current group will be rendered at least once, and if there are any sibling groups deeper
|
|
// than the current group, we will repeat rendering the current group to fill up the gap between
|
|
// current & deepest sibling.
|
|
var deepestInSiblings = currentGroup.findDeepestGroupInChildren(skipIndex).getCurrentGroupDepth();
|
|
// Find out the depth difference between the deepest group under the siblings of currentGroup and
|
|
// the deepest group under currentGroup. If the result n is a positive number, it means there are
|
|
// deeper groups in siblings and we need to draw n + 1 group blocks on UI to fill up the depth
|
|
// differences. If the result n is a negative number, it means current group contains the deepest
|
|
// sub-group, we only need to draw the group block once.
|
|
var repeatCount = Math.max(deepestInSiblings - lastDepth, 0);
|
|
|
|
for (var i = 0; i <= repeatCount; i++) {
|
|
var isLeftMost = isLeftMostPath();
|
|
var isRightMost = isRightMostPath();
|
|
var groupViewModel = new ClauseGroupViewModel(currentGroup, i === 0 && isLeftMost, this);
|
|
|
|
groupViewModel.showTopBorder(isLeftMost);
|
|
groupViewModel.showBottomBorder(isRightMost);
|
|
groupViewModel.showLeftBorder(i === repeatCount);
|
|
groupViewModels[vmIndex] = groupViewModel;
|
|
vmIndex--;
|
|
}
|
|
|
|
skipIndex = currentGroup.parentGroup.children.indexOf(currentGroup);
|
|
currentGroup = currentGroup.parentGroup;
|
|
lastDepth = Math.max(deepestInSiblings, lastDepth);
|
|
}
|
|
|
|
return groupViewModels;
|
|
};
|
|
|
|
public runQuery = (): DataTables.DataTable => {
|
|
return this._queryViewModel.runQuery();
|
|
};
|
|
|
|
public addCustomRange(timestamp: CustomTimestampHelper.ITimestampQuery, clauseToAdd: QueryClauseViewModel): void {
|
|
var index = this.clauseArray.peek().indexOf(clauseToAdd);
|
|
|
|
var newClause = new QueryClauseViewModel(
|
|
this,
|
|
//this._tableEntityListViewModel.tableExplorerContext.hostProxy,
|
|
"And",
|
|
clauseToAdd.field(),
|
|
"DateTime",
|
|
Constants.Operator.LessThan,
|
|
"",
|
|
true,
|
|
Constants.timeOptions.custom,
|
|
timestamp.endTime,
|
|
"range",
|
|
//null,
|
|
true
|
|
);
|
|
|
|
newClause.isLocal = ko.observable(timestamp.timeZone === "local");
|
|
this.addClauseImpl(newClause, index + 1);
|
|
|
|
if (index + 1 === this.clauseArray().length - 1) {
|
|
this.scrollToBottom();
|
|
}
|
|
}
|
|
|
|
private scrollToBottom(): void {
|
|
var scrollBox = document.getElementById("scroll");
|
|
if (!this.scrollEventListener) {
|
|
scrollBox.addEventListener("scroll", function () {
|
|
var translate = "translate(0," + this.scrollTop + "px)";
|
|
const allTh = <NodeListOf<HTMLElement>>this.querySelectorAll("thead td");
|
|
for (let i = 0; i < allTh.length; i++) {
|
|
allTh[i].style.transform = translate;
|
|
}
|
|
});
|
|
this.scrollEventListener = true;
|
|
}
|
|
var isScrolledToBottom = scrollBox.scrollHeight - scrollBox.clientHeight <= scrollBox.scrollHeight + 1;
|
|
if (isScrolledToBottom) {
|
|
scrollBox.scrollTop = scrollBox.scrollHeight - scrollBox.clientHeight;
|
|
}
|
|
}
|
|
|
|
private addClauseImpl(clause: QueryClauseViewModel, position: number): void {
|
|
this.queryClauses.insertClauseBefore(clause, this.clauseArray()[position]);
|
|
this.updateClauseArray();
|
|
}
|
|
|
|
private deleteClauseImpl(index: number): void {
|
|
var clause = this.clauseArray()[index];
|
|
var previousClause = index === 0 ? 0 : index - 1;
|
|
this.queryClauses.deleteClause(clause);
|
|
this.updateClauseArray();
|
|
if (this.clauseArray()[previousClause]) {
|
|
this.clauseArray()[previousClause].isDeleteButtonFocused(true);
|
|
}
|
|
}
|
|
|
|
public updateCanGroupClauses(): void {
|
|
this.canGroupClauses(this.queryClauses.canGroupSelectedItems());
|
|
}
|
|
|
|
public updateClauseArray(): void {
|
|
if (this.clauseArray().length > 0) {
|
|
this.clauseArray()[0].canAnd(true);
|
|
}
|
|
|
|
this.queryClauses.flattenClauses(this.clauseArray);
|
|
|
|
if (this.clauseArray().length > 0) {
|
|
this.clauseArray()[0].canAnd(false);
|
|
}
|
|
|
|
// Fix for 261924, forces the resize event so DataTableBindingManager will redo the calculation on table size.
|
|
//DataTableUtilities.forceRecalculateTableSize();
|
|
}
|
|
|
|
private timestampToValue(clause: QueryClauseViewModel): void {
|
|
if (clause.isValue()) {
|
|
return;
|
|
} else if (clause.isTimestamp()) {
|
|
this.getTimeStampToQuery(clause);
|
|
// } else if (clause.isCustomLastTimestamp()) {
|
|
// clause.value(`datetime'${CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit)}'`);
|
|
} else if (clause.isCustomRangeTimestamp()) {
|
|
if (clause.isLocal()) {
|
|
clause.value(`datetime'${DateTimeUtilities.getUTCDateTime(clause.customTimeValue())}'`);
|
|
} else {
|
|
clause.value(`datetime'${clause.customTimeValue()}Z'`);
|
|
}
|
|
}
|
|
}
|
|
|
|
private timestampToSqlValue(clause: QueryClauseViewModel): string {
|
|
if (clause.isValue()) {
|
|
return null;
|
|
} else if (clause.isTimestamp()) {
|
|
return this.getTimeStampToSqlQuery(clause);
|
|
// } else if (clause.isCustomLastTimestamp()) {
|
|
// clause.value(CustomTimestampHelper._queryLastTime(clause.customLastTimestamp().lastNumber, clause.customLastTimestamp().lastTimeUnit));
|
|
} else if (clause.isCustomRangeTimestamp()) {
|
|
if (clause.isLocal()) {
|
|
return DateTimeUtilities.getUTCDateTime(clause.customTimeValue());
|
|
} else {
|
|
return clause.customTimeValue();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private getTimeStampToQuery(clause: QueryClauseViewModel): void {
|
|
switch (clause.timeValue()) {
|
|
case Constants.timeOptions.lastHour:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 1)}'`);
|
|
break;
|
|
case Constants.timeOptions.last24Hours:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(0, 24)}'`);
|
|
break;
|
|
case Constants.timeOptions.last7Days:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(7, 0)}'`);
|
|
break;
|
|
case Constants.timeOptions.last31Days:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(31, 0)}'`);
|
|
break;
|
|
case Constants.timeOptions.last365Days:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryLastDaysHours(365, 0)}'`);
|
|
break;
|
|
case Constants.timeOptions.currentMonth:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryCurrentMonthLocal()}'`);
|
|
break;
|
|
case Constants.timeOptions.currentYear:
|
|
clause.value(`datetime'${CustomTimestampHelper._queryCurrentYearLocal()}'`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private getTimeStampToSqlQuery(clause: QueryClauseViewModel): string {
|
|
switch (clause.timeValue()) {
|
|
case Constants.timeOptions.lastHour:
|
|
return CustomTimestampHelper._queryLastDaysHours(0, 1);
|
|
case Constants.timeOptions.last24Hours:
|
|
return CustomTimestampHelper._queryLastDaysHours(0, 24);
|
|
case Constants.timeOptions.last7Days:
|
|
return CustomTimestampHelper._queryLastDaysHours(7, 0);
|
|
case Constants.timeOptions.last31Days:
|
|
return CustomTimestampHelper._queryLastDaysHours(31, 0);
|
|
case Constants.timeOptions.last365Days:
|
|
return CustomTimestampHelper._queryLastDaysHours(365, 0);
|
|
case Constants.timeOptions.currentMonth:
|
|
return CustomTimestampHelper._queryCurrentMonthLocal();
|
|
case Constants.timeOptions.currentYear:
|
|
return CustomTimestampHelper._queryCurrentYearLocal();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public checkIfClauseChanged(): void {
|
|
this._queryViewModel.checkIfBuilderChanged();
|
|
}
|
|
}
|