Files
dc/client/src/app/query/query.component.ts
T
dc cc01f540c2
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m55s
Build / Build-and-test-development (pull_request) Successful in 29m58s
fix(filters): use the unformatted value in every IN branch
Finding from the review at ece67d3.

dynamicWhereClauseCreator's IN branch mapped the entry through element.values
only when building the literal; the two fallbacks emitted the unmapped value, so
a formatted label could still reach RAW_VALUE.  A temporal entry whose mapped
value is not a whole day / second (sasTemporalLiteral returns null for a
genuinely fractional value) or whose label finds no match in element.values
would fall through to the quoted label on a numeric column, which mp_filtercheck
rejects and which aborts the getcolvals load inside the swallowed catch.

The mapped value is now assigned once and used in all three branches, as
createFilterQueryTable's IN branch does.

One unit case added for the non-literal fallback.  582/582 unit tests pass.
2026-10-16 21:44:08 +00:00

1125 lines
32 KiB
TypeScript

import { Subscription } from 'rxjs'
import {
Component,
AfterViewInit,
AfterContentInit,
OnDestroy,
ChangeDetectorRef,
LOCALE_ID,
Input,
ViewEncapsulation
} from '@angular/core'
import { SasStoreService } from '../services/sas-store.service'
import { globals } from '../_globals'
import { EventService } from '../services/event.service'
import { registerLocaleData } from '@angular/common'
import localeEnGB from '@angular/common/locales/en-GB'
import { HelperService } from '../services/helper.service'
import { QueryClause } from '../models/TableData'
import { PublicGetcolvalsSASResponse } from '../models/sas/public-getcolvals.model'
import { QueryDateTime } from './models/QueryDateTime.model'
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import { get } from 'lodash-es'
import { OnLoadingMoreEvent } from '../shared/autocomplete/autocomplete.component'
import { getFilterObjPath } from './utils/getFilterObjPath'
registerLocaleData(localeEnGB)
@Component({
selector: 'app-query',
templateUrl: './query.component.html',
styleUrls: ['./query.component.scss'],
providers: [{ provide: LOCALE_ID, useValue: 'en-GB' }],
encapsulation: ViewEncapsulation.None,
standalone: false
})
export class QueryComponent
implements AfterViewInit, AfterContentInit, OnDestroy
{
@Input() caching: boolean = true
@Input() viewboxId: number | undefined
private rows_increment: number = 100
private initial_rows: number = 1000
private columnsSub: Subscription | undefined
private valuesSub: Subscription | undefined
private _removeQry: Subscription | undefined
public cols: Array<any> | undefined
public libds: string | undefined
public variable: string | undefined
public value: string | undefined
public operator: string | undefined
public vals: Array<any> | undefined
public innerWidth: any
public noBorder: any
public clauseAmt: Array<any> = ['']
public numOperators: Array<any> = [
'=',
'<',
'>',
'<=',
'>=',
'BETWEEN',
'IN',
'NOT IN',
'NE'
]
public charOperators: Array<any> = [
'=',
'<',
'>',
'<=',
'>=',
'CONTAINS',
'IN',
'NOT IN',
'NE'
// "LIKE",
// "BEGINS_WITH",
]
public logic: string | undefined
public notIn: boolean = false
public notArr: boolean = true
public wasBetween: boolean = false
public whereClauseLoading: boolean = false
public dynamicWhereClause: boolean = true
public usePickers: boolean = false
private queryObj: {
elements: any[]
clauseLogic?: string
invalidClause?: boolean
} = {
elements: [
{
logic: null,
type: null,
ddtype: null,
variable: null,
operator: null,
value: null,
valueVariable: false,
startrow: 0,
rows: 0,
nobs: 0,
values: <
{
formatted: any
unformatted: any
}[]
>[],
operators: []
}
],
clauseLogic: '',
invalidClause: false
}
public selVar: any = {
index: null,
format: null,
type: null
}
public inObj: Array<any> = [
{
checked: false,
value: null
}
]
public groupLogic: string = 'AND'
public clauses: any = {
queryObj: [this.queryObj],
clauseLogic: [],
groupLogic: this.groupLogic
}
public whereString: string | undefined
public val: any
public whereClause: string | undefined
public logicOperators: Array<string> = ['AND', 'OR']
/**
* Temporary values array used in pickers
* because they need particular format to work
* before sending values to backend, values are parsed
*/
public queryDateTime: QueryDateTime[] = []
public currentClauseIndex: number = -1
public currentQueryIndex: number = -1
public pendingINValuesSet: {
clauseIndex: number
queryIndex: number
} | null = null
constructor(
private sasStoreService: SasStoreService,
private eventService: EventService,
private helperService: HelperService,
private cdf: ChangeDetectorRef
) {
this.innerWidth = window.screen.width
if (this.innerWidth > 768) {
this.noBorder = 'border-left: 2px solid gray;'
}
}
/**
* Gets and sets temporary values selected with DATETIME or TIME picker
* Those values are used for picker to work with format it lieks
* Later before sending values to backend, values are parsed
*/
getQueryDateTime(clauseIndex: number, queryIndex: number): QueryDateTime {
let existingQueryDateTime = this.queryDateTime.find(
(x) => x.clauseIndex === clauseIndex && x.queryIndex === queryIndex
)
if (!existingQueryDateTime) {
const lastIndex =
this.queryDateTime.push({
clauseIndex,
queryIndex,
date: '',
time: ''
}) - 1
existingQueryDateTime = this.queryDateTime[lastIndex]
}
return existingQueryDateTime
}
/**
* When toggling pickers feature we reset the temp picker values array
*/
usePickersChange() {
this.queryDateTime = []
if (this.usePickers) {
this.clauses.queryObj.forEach((queryObj: any) => {
queryObj.elements.forEach((element: any) => {
const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(
element.ddtype
)
if (isDateOrTime && element.valueVariable) {
element.value = ''
element.valueVariable = false
}
})
})
}
}
/**
* Resets all variables used for filtering
*/
public resetFilter() {
this.whereString = undefined
this.whereClause = undefined
this.queryObj = {
elements: [
{
logic: null,
type: null,
ddtype: null,
variable: null,
operator: null,
value: null,
values: [],
operators: []
}
]
}
this.clauses = {
queryObj: [this.queryObj],
clauseLogic: [],
groupLogic: this.groupLogic
}
this.whereClauseFn(true)
}
/**
* `Globals` are used to store filtering state (variables) as a caching feature
* until browser reloads
*/
public setToGlobals() {
if (!this.caching) return
const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)
get(globals, objPath).filter.groupLogic = this.groupLogic
if (typeof this.whereClause === 'string') {
get(globals, objPath).filter.whereClause = this.whereClause
}
if (typeof this.libds === 'string') {
get(globals, objPath).filter.libds = this.libds
}
get(globals, objPath).filter.clauses = this.clauses
}
/**
* `Globals` are used to store filtering state (variables) as a caching feature
* until browser reloads
*/
public getFromGlobals() {
if (!this.caching) return
const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)
if (get(globals, objPath).filter.cols.length > 0) {
this.cols = JSON.parse(JSON.stringify(get(globals, objPath).filter.cols))
}
if (get(globals, objPath).filter.vals.length > 0) {
this.vals = JSON.parse(JSON.stringify(get(globals, objPath).filter.vals))
}
if (get(globals, objPath).filter.groupLogic !== '') {
this.groupLogic = get(globals, objPath).filter.groupLogic
this.whereClause = get(globals, objPath).filter.whereClause
this.libds = get(globals, objPath).filter.libds
this.clauses = get(globals, objPath).filter.clauses
this.whereClauseFn()
}
}
/**
* Sets filtering multiple caluses group logic (and / or)
*
* @param groupLogic to set
*/
public setGroupLogic(groupLogic: any) {
this.groupLogic = groupLogic
this.clauses.groupLogic = groupLogic
this.whereClauseFn()
this.setToGlobals()
}
public dateChange(
pickerValue: any,
query: any,
queryIndex: number,
clauseIndex: number,
queryValueIndex?: number
) {
let valueInDays = this.helperService.convertJsDateToSasDate(pickerValue)
if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
queryValueIndex
] = valueInDays
}
this.setVariableValues(valueInDays, queryIndex, clauseIndex)
}
public dateTimeChange(
query: any,
queryIndex: number,
clauseIndex: number,
queryValueIndex?: number
) {
const queryDateTime = this.getQueryDateTime(clauseIndex, queryIndex)
if (queryDateTime.date === '') {
if (query.value === '') return
queryDateTime.date = query.value.split(':')[0]
}
const dateHours = parseInt(queryDateTime.time.split(':')[0])
const dateMinutes = parseInt(queryDateTime.time.split(':')[1])
const dateSeconds = parseInt(queryDateTime.time.split(':')[2]) || 0
let dateObject = new Date(queryDateTime.date)
let dateTimeObject = new Date(
dateObject.getFullYear(),
dateObject.getMonth(),
dateObject.getDate(),
dateHours,
dateMinutes,
dateSeconds
)
let valueInSeconds = this.helperService.convertJsDateToSasDate(
dateTimeObject,
'seconds'
)
if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
queryValueIndex
] = valueInSeconds
}
this.setVariableValues(valueInSeconds, queryIndex, clauseIndex)
}
public timeChange(
time: any,
query: any,
ind: number,
clauseIndex: number,
queryValueIndex?: number
) {
time = time.target.value
let hours = parseInt(time.split(':')[0])
let minutes = parseInt(time.split(':')[1])
let seconds = parseInt(time.split(':')[2]) || 0
let hourSeconds = hours * 60 * 60
let minuteSeconds = minutes * 60
let inputValue = hourSeconds + minuteSeconds + seconds
if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
this.clauses.queryObj[clauseIndex].elements[ind].value[queryValueIndex] =
inputValue
}
this.setVariableValues(inputValue, ind, clauseIndex)
}
/**
* Takes the clause object, removes current clause and makes new array (table)
* ready to be sent to SAS
*/
public dynamicWhereClauseCreator(groupIndex: number, clauseIndex: number) {
let dynamicWhereClauseTable: QueryClause[] = []
if (
this.clauses.queryObj.length > 1 ||
this.clauses.queryObj[groupIndex].elements.length > 1
) {
for (
let groupInd = 0;
groupInd < this.clauses.queryObj.length;
groupInd++
) {
for (
let clauseInd = 0;
clauseInd < this.clauses.queryObj[groupInd].elements.length;
clauseInd++
) {
if (groupInd === groupIndex && clauseInd === clauseIndex) continue
if (
[null, undefined].includes(
this.clauses.queryObj[groupInd].elements[clauseInd].variable
) ||
[null, undefined].includes(
this.clauses.queryObj[groupInd].elements[clauseInd].operator
) ||
[null, undefined].includes(
this.clauses.queryObj[groupInd].elements[clauseInd].value
)
)
continue
let rawValue = ''
const element = this.clauses.queryObj[groupInd].elements[clauseInd]
const operator = element.operator
const variable = element.variable
/* a temporal value is sent as a SAS literal (eg '20APR2042'd), the
same way the applied filter carries it */
const temporalValue = (value: any) =>
element.valueVariable
? null
: this.helperService.sasTemporalLiteral(value, element.ddtype)
/* the dropdowns and the IN modal hold the *formatted* value (the
soft-select option value, and setINValues), so a picked value is
mapped back to the unformatted one the filter needs */
const unformattedValue = (value: any) =>
(element.values || []).find((v: any) => v.formatted === value)
?.unformatted ?? value
if (operator === 'BETWEEN') {
const from = unformattedValue(element.value[0])
const to = unformattedValue(element.value[1])
rawValue = `${temporalValue(from) ?? from} AND ${
temporalValue(to) ?? to
}`
} else if (operator === 'IN' || operator === 'NOT IN') {
element.value.forEach((item: any) => {
if (item.checked) {
/* map once, and use the unformatted value in every branch - the
literal is built from it, and so are the fallbacks, so a
formatted label can never reach RAW_VALUE */
const value = unformattedValue(item.val)
const literal = temporalValue(value)
if (literal) {
rawValue += (rawValue.length > 0 ? ',' : '(') + literal
} else if (
typeof value === 'string' &&
!isSpecialMissing(value)
) {
if (rawValue.length > 0) {
rawValue += `,'${value}'`
} else {
rawValue = `('${value}'`
}
} else {
if (rawValue.length > 0) {
rawValue += `,${value}`
} else {
rawValue = `(${value}`
}
}
}
})
if (rawValue.length > 0) {
rawValue += ')'
}
} else if (
this.clauses.queryObj[groupInd].elements[clauseInd].value === ''
) {
if (
this.clauses.queryObj[groupInd].elements[clauseInd].type ===
'char'
) {
rawValue = `' '`
} else {
rawValue = '.'
}
} else {
const literal = temporalValue(element.value)
if (literal) {
rawValue = literal
} else if (element.type === 'char') {
rawValue = `'${element.value}'`
}
}
dynamicWhereClauseTable.push({
GROUP_LOGIC: this.clauses.groupLogic,
SUBGROUP_LOGIC:
this.clauses.queryObj[groupInd].clauseLogic || 'AND',
SUBGROUP_ID: groupInd,
VARIABLE_NM: variable,
OPERATOR_NM: operator,
RAW_VALUE:
rawValue ||
this.clauses.queryObj[groupInd].elements[clauseInd].value
})
}
}
}
return dynamicWhereClauseTable
}
public async getValuesLoadMore(
event: OnLoadingMoreEvent,
variable: any,
ind: any,
clauseIndex: any
) {
const libds = this.libds || ''
try {
if (this.cols !== undefined) {
for (let index = 0; index < this.cols.length; index++) {
let element = this.cols[index]
if (element.NAME === variable) {
this.selVar.index = index
}
}
const DDTYPE = this.cols[this.selVar.index].DDTYPE
const { startrow: currentStartrow, rows: currentRows } =
this.clauses.queryObj[clauseIndex].elements[ind]
const ROWS_INCREMENT: number = this.rows_increment
const STARTROW = currentStartrow + currentRows
let dynamicWhereClauseTable: any[] = []
if (this.dynamicWhereClause) {
dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
clauseIndex,
ind
)
}
const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)
this.sasStoreService
.getQueryValues(
variable,
libds,
dynamicWhereClauseTable,
STARTROW,
ROWS_INCREMENT
)
.then((res: PublicGetcolvalsSASResponse) => {
const values = res.vals
const { STARTROW, ROWS, NOBS } = res.meta[0]
this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS
if (
!(
values.length === 1 &&
values[0].FORMATTED === '' &&
values[0].UNFORMATTED === ''
)
) {
this.clauses.queryObj[clauseIndex].elements[ind].values.push(
...values.map((item: any) => {
const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
const specialMissing =
numeric && isSpecialMissing(item.UNFORMATTED)
let unformatted = specialMissing
? '.' + item.UNFORMATTED
: item.UNFORMATTED
// If it's not special missing and is numeric and null then it is normal missing `.`
if (numeric && unformatted === null) unformatted = '.'
return {
formatted: isDateOrTime
? item.FORMATTED.split('.')[0]
: item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
unformatted: unformatted
}
})
)
const currentValues =
this.clauses.queryObj[clauseIndex].elements[ind].values.length
const moreExists = NOBS - currentValues > 0
event.loadMoreFinished(moreExists)
} else {
event.loadMoreFinished(false)
}
this.whereClauseFn()
this.setToGlobals()
this.whereClauseLoading = false
/**
* If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
* Othervise values are not beign populated
*/
if (this.pendingINValuesSet !== null) {
const { clauseIndex, queryIndex } = this.pendingINValuesSet
this.setINValues(clauseIndex, queryIndex)
this.pendingINValuesSet = null
}
this.cdf.detectChanges()
})
.catch((err: any) => {
this.whereClauseLoading = false
})
}
} catch (error) {
this.eventService.catchResponseError('public/getcolvals', error)
}
}
public async getValues(variable: any, ind: any, clauseIndex: any) {
this.whereClauseLoading = true
let libds: string = ''
if (this.libds !== undefined) {
libds = this.libds
}
this.clearValues(ind, clauseIndex)
this.clauses.queryObj[clauseIndex].elements[ind].values = []
this.clauses.queryObj[clauseIndex].elements[ind].value = ''
this.clauses.queryObj[clauseIndex].elements[ind].operator = '='
this.clauses.queryObj[clauseIndex].elements[ind].variable = variable
try {
if (this.cols !== undefined) {
for (let index = 0; index < this.cols.length; index++) {
let element = this.cols[index]
if (element.NAME === variable) {
this.selVar.index = index
}
}
const DDTYPE = this.cols[this.selVar.index].DDTYPE
this.clauses.queryObj[clauseIndex].elements[ind].ddtype = DDTYPE
this.clauses.queryObj[clauseIndex].elements[ind].type =
this.cols[this.selVar.index].TYPE
if (this.clauses.queryObj[clauseIndex].elements[ind].type === 'num') {
this.clauses.queryObj[clauseIndex].elements[ind].operators =
this.numOperators
} else {
this.clauses.queryObj[clauseIndex].elements[ind].operators =
this.charOperators
}
let dynamicWhereClauseTable: any[] = []
if (this.dynamicWhereClause) {
dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
clauseIndex,
ind
)
}
const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)
this.sasStoreService
.getQueryValues(
variable,
libds,
dynamicWhereClauseTable,
1,
this.initial_rows
)
.then((res: PublicGetcolvalsSASResponse) => {
const values = res.vals
const { STARTROW, ROWS, NOBS } = res.meta[0]
this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS
this.clauses.queryObj[clauseIndex].elements[ind].values = values
.map((item: any) => {
const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
const specialMissing =
numeric && isSpecialMissing(item.UNFORMATTED)
let unformatted = specialMissing
? '.' + item.UNFORMATTED
: item.UNFORMATTED
// If it's not special missing and is numeric and null then it is normal missing `.`
if (numeric && unformatted === null) unformatted = '.'
return {
formatted: isDateOrTime
? item.FORMATTED.split('.')[0]
: item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
unformatted: unformatted
}
})
.slice(0, 2000)
this.whereClauseFn()
this.setToGlobals()
this.whereClauseLoading = false
/**
* If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
* Othervise values are not beign populated
*/
if (this.pendingINValuesSet !== null) {
const { clauseIndex, queryIndex } = this.pendingINValuesSet
this.setINValues(clauseIndex, queryIndex)
this.pendingINValuesSet = null
}
this.cdf.detectChanges()
})
.catch((err: any) => {
this.whereClauseLoading = false
})
}
} catch (error) {
this.eventService.catchResponseError('public/getcolvals', error)
}
}
public getlogic(logic: any) {
this.logic = logic
this.whereClauseFn()
}
public setLogic() {
this.whereClauseFn()
this.setToGlobals()
}
public isArr(val: any) {
return (
val instanceof Array &&
val.length > 0 &&
typeof val[0].checked !== 'undefined'
)
}
public selectedLabelChange(label: string, query: any) {
query.valueVariable = label === 'Variable'
query.value = ''
this.whereClauseFn()
}
public variableInputChange(
queryVariable: any,
index: number,
clauseIndex: number,
event: any
) {
this.getValues(queryVariable, index, clauseIndex)
}
public setVariableValues(valueObj: any, ind: any, clauseIndex: any) {
// clauseIndex is current clause in group clauses, ind is current query in clause
let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator
if (operator !== 'BETWEEN' && operator !== 'IN' && operator !== 'NOT IN') {
this.clauses.queryObj[clauseIndex].elements[ind].value =
valueObj.toString()
}
this.whereClauseFn()
this.setToGlobals()
}
public setVariableOperator(ind: any, operator: any, clauseIndex: any) {
let currentValue = this.clauses.queryObj[clauseIndex].elements[ind].value
if (
currentValue instanceof Array &&
operator !== 'IN' &&
operator !== 'NOT IN'
) {
if (
currentValue instanceof Array &&
currentValue.length === 2 &&
typeof currentValue[0] === 'string'
) {
currentValue = currentValue[0]
} else {
currentValue = ''
}
}
this.clearValuesOperator(ind, clauseIndex)
this.clauses.queryObj[clauseIndex].elements[ind].operator = operator
// tslint:disable-next-line:max-line-length
if (
operator === '=' ||
operator === '>' ||
operator === '<' ||
operator === '<=' ||
operator === '>=' ||
operator === 'NE'
) {
this.clauses.queryObj[clauseIndex].elements[ind].value = currentValue
}
if (operator === 'BETWEEN') {
this.clauses.queryObj[clauseIndex].elements[ind].value = []
this.clauses.queryObj[clauseIndex].elements[ind].value.push(currentValue)
this.clauses.queryObj[clauseIndex].elements[ind].value.push('')
}
if (operator === 'IN' || operator === 'NOT IN') {
this.setINValues(clauseIndex, ind)
}
this.whereClauseFn()
this.setToGlobals()
}
public setINValues(clauseIndex: number, queryIndex: number) {
const vals = []
const inValues =
this.clauses.queryObj[clauseIndex].elements[queryIndex].values
if (inValues.length < 1) {
this.pendingINValuesSet = {
clauseIndex,
queryIndex
}
}
for (let index = 0; index < inValues.length; index++) {
vals.push({ checked: false, val: inValues[index].formatted })
}
this.clauses.queryObj[clauseIndex].elements[queryIndex].value = vals
}
public clearValues(ind: any, clauseIndex: any) {
this.clauses.queryObj[clauseIndex].elements[ind].value = ''
}
public clearValuesOperator(ind: any, clauseIndex: any) {
let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator
if (
operator === 'BETWEEN' ||
operator === 'IN' ||
operator === 'NOT IN' ||
operator === 'CONTAINS'
) {
this.clearValues(ind, clauseIndex)
}
}
/**
* Checks if clause is invalid and sets bool property accordingly
* It is being used in html to mark invalid clauses red
*/
public hasInvalidCluase(clauses: any): boolean {
for (let clause of clauses) {
clause['invalidClause'] = false
if (
clause.value === '' &&
!(clause.operator === 'NE' || clause.operator === 'CONTAINS')
) {
clause['invalidClause'] = true
return true
}
if (
clause.variable === null ||
clause.operator === null ||
clause.value === null
) {
clause['invalidClause'] = true
return true
}
}
clauses.invalidClause = false
return false
}
/**
* Checks if group clause is invalid and sets bool property accordingly
* It is being used in html to mark invalid group clauses red
*/
public hasInvalidGroupCluase(): boolean {
for (let i = 0; i < this.clauses.queryObj.length; i++) {
if (this.hasInvalidCluase(this.clauses.queryObj[i].elements)) {
this.clauses.queryObj[i].invalidClause = true
return true
} else {
this.clauses.queryObj[i].invalidClause = false
}
}
return false
}
public addClause(clauseIndex: number) {
if (this.hasInvalidCluase(this.clauses.queryObj[clauseIndex].elements)) {
this.eventService.showInfoModal(
'Error',
'Cannot add new clause, when one or more clauses are invalid or empty.'
)
return
}
this.clauseAmt.push('')
this.clauses.queryObj[clauseIndex].elements.push({
logic: null,
type: null,
variable: null,
operator: null,
value: null,
values: [],
operators: []
})
let clauseLogic = this.clauses.queryObj[clauseIndex].clauseLogic
if (typeof clauseLogic === 'undefined') {
this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
} else if (clauseLogic === 'OR') {
this.clauses.queryObj[clauseIndex].clauseLogic = 'OR'
} else {
this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
}
this.whereClauseFn()
this.setToGlobals()
}
public addGroupClause() {
if (this.hasInvalidGroupCluase()) {
this.eventService.showInfoModal(
'Error',
'Cannot add new clause, when one or more clauses are invalid or empty.'
)
return
}
this.clauses.queryObj.push({
elements: [
{
logic: null,
type: null,
variable: null,
operator: null,
value: null,
values: [],
operators: []
}
]
})
this.setToGlobals()
}
public isInHtmlCollection(
value: string,
collection: HTMLCollectionOf<HTMLOptionElement>
): boolean {
for (let i = 0; i < collection.length; i++) {
if (collection[i].value === value) return true
}
return false
}
public removeGroupClause(clauseIndex: number) {
this.clauses.queryObj.splice(clauseIndex, 1)
this.whereClauseFn()
}
public removeClause(ind: number, clauseIndex: any) {
this.clauses.queryObj[clauseIndex].elements.splice(ind, 1)
this.whereClauseFn()
this.setToGlobals()
}
public whereClauseFn(reset: boolean = false) {
const clauses = this.helperService.deepClone(this.clauses)
if (reset) clauses.queryObj[0].elements = []
if (this.libds !== undefined) {
let result = this.sasStoreService.whereClauseCreator(
clauses,
this.groupLogic,
this.libds
)
if (!reset) {
this.whereClause = result.whereClause
this.whereString = result.string
}
}
}
async ngAfterViewInit() {
this._removeQry = this.sasStoreService.removeQuery.subscribe((remove) => {
let res = remove
if (this.clauses.queryObj.length >= 1) {
this.clauses.queryObj.length = 1
if (this.clauses.queryObj[0].elements.length >= 1) {
this.clauses.queryObj[0].elements.length = 1
this.clauses.queryObj[0].elements = []
this.clauses.queryObj[0].elements.push({
logic: null,
type: null,
variable: null,
operator: null,
value: null,
values: [],
operators: []
})
this.whereClauseFn()
}
}
})
this.columnsSub = this.sasStoreService.columns.subscribe(
(response: any) => {
let cols = response.data.cols
const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)
if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
this.cols = cols
let some = cols[0].NAME
this.libds = response.libds
get(globals, objPath).filter.cols = JSON.parse(JSON.stringify(cols))
}
if (globals.rootParam === 'view') {
if (get(globals, objPath).filter.cols.length < 1) {
this.cols = cols
let some = cols[0].NAME
this.libds = response.libds
get(globals, objPath).filter.cols = JSON.parse(JSON.stringify(cols))
}
}
if (!this.libds) {
this.libds = response.libds
}
}
)
this.valuesSub = this.sasStoreService.values.subscribe((res: any) => {
const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)
if (get(globals, objPath).filter.vals.length < 1) {
this.vals = res.vals
get(globals, objPath).filter.vals = JSON.parse(JSON.stringify(res.vals))
}
})
}
onAutocompleteLoadingMore(
event: OnLoadingMoreEvent,
variable: any,
ind: any,
clauseIndex: any
) {
this.getValuesLoadMore(event, variable, ind, clauseIndex)
}
ngAfterContentInit(): void {
this.getFromGlobals()
setTimeout(() => {
let varInput: HTMLElement | null =
document.querySelector('#vals_var_id0_0')
if (varInput) varInput.focus()
}, 500)
}
ngOnDestroy(): void {
if (this.columnsSub) {
this.columnsSub.unsubscribe()
}
if (this.valuesSub) {
this.valuesSub.unsubscribe()
}
}
}