Files
dc/client/src/app/services/sas-store.service.ts
T
dc 8f98ebe326
Build / Build-and-ng-test (pull_request) Successful in 5m18s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m51s
Build / Build-and-test-development (pull_request) Successful in 30m50s
fix(filters): map an IN entry to its unformatted value before the literal
Finding from the review at be79f3a.

createFilterQueryTable's IN / NOT IN branch read element.val directly, so it
produced a temporal literal only because whereClauseCreator had already mutated
query.value[i].val from the formatted label to the unformatted count in place
before submit.  Any path reaching the branch without the preview having run
first would have put the formatted string into RAW_VALUE.

The entry now goes through the same unformattedValue() lookup as the BETWEEN
ends.  An already-unformatted value passes through unchanged, since it no longer
matches a formatted label.

Two unit cases added: an IN entry carrying the formatted label, and one already
carrying the internal count.  578/578 unit tests pass.
2026-10-16 20:21:38 +00:00

965 lines
30 KiB
TypeScript

import { Injectable } from '@angular/core'
import { Subject } from 'rxjs'
import { SpecObj } from '../interfaces'
import { SasService } from './sas.service'
import { HelperService } from './helper.service'
import { QueryClause } from '../models/TableData'
import { globals } from '../_globals'
import { FilterClause, FilterGroup, FilterQuery } from '../models/FilterQuery'
import {
$DataFormats,
EditorsGetDataSASResponse,
EditorsGetDataServiceResponse
} from '../models/sas/editors-getdata.model'
import { LoggerService } from './logger.service'
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import { Col } from '../shared/dc-validator/models/col.model'
import { get } from 'lodash-es'
import { EditorsStageDataSASResponse } from '../models/sas/editors-stagedata.model'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
@Injectable()
export class SasStoreService {
public response: Subject<any> = new Subject<any>()
public changedTable: Subject<any> = new Subject<any>()
public details: Subject<any> = new Subject<any>()
public diffs: Subject<any> = new Subject<any>()
public columns: Subject<any> = new Subject<any>()
public values: Subject<any> = new Subject<any>()
public filter: Subject<any> = new Subject<any>()
public query: Subject<any> = new Subject<any>()
public submittDetail: Subject<any> = new Subject<any>()
public removeQuery: Subject<any> = new Subject<any>()
public setSubmit: Subject<any> = new Subject<any>()
public setSubmitList: Subject<any> = new Subject<any>()
/**
* Submit queue payload carried across the navigation from the submit
* list to a details URL - the re-created list component renders it
* without fetching the queue again.
*/
private submitsHandoff: Array<any> | null = null
constructor(
private sasService: SasService,
private helperService: HelperService,
private loggerService: LoggerService
) {}
/**
* Wrapper for making request to service
* Should be removed, as it's redundant now
* TODO: Refactor to call editors/getdata directly
* @param tableData
* @param tableName
* @param program
* @param libds
* @returns
*/
public async callService(
tableData: Array<any>,
tableName: string,
program: string,
libds: string
) {
const tables: any = {}
tables[tableName] = [tableData]
const res: RequestWrapperResponse<EditorsGetDataSASResponse> =
await this.sasService.request(program, tables)
const response: EditorsGetDataServiceResponse = {
data: res.adapterResponse,
libds: libds
}
return response
}
/**
* Calling editors/stagedata - saving table data, sending request to backend
* @param tableParams params to send to backend
* @param tableData data to be updated
* @param tableName name of the table to be updated
* @param program service against which we send request
* @param $dataFormats column data formats recieved from backend, sending it back
* @returns adapter.request() response
*/
public async updateTable(
tableParams: any,
tableData: any,
tableName: string,
program: string,
$dataFormats: $DataFormats | null,
suppressErrorSuccessMessages?: boolean,
adapterConfig?: any
): Promise<RequestWrapperResponse<EditorsStageDataSASResponse>> {
// add sp as third argument of createData call
let tables: any = {
jsdata: tableData
}
if ($dataFormats) {
let formats = this.parseFormats($dataFormats)
tables.$jsdata = { formats: formats }
}
tables[tableName] = [tableParams]
let res = await this.sasService.request<EditorsStageDataSASResponse>(
program,
tables,
adapterConfig,
{
suppressErrorAbortModal: suppressErrorSuccessMessages,
suppressSuccessAbortModal: suppressErrorSuccessMessages
}
)
return res
}
/**
* Sending request to 'approvers/getapprovals' to fetch approvals list
* @param tableData sending to backend table data
* @param tableName sending to backend table name
* @param program service to run request on
* @returns HTTP Response
*/
public async getApprovals(
tableData: any,
tableName: string,
program: string
) {
let tables: any = {}
tables[tableName] = [tableData]
return (await this.sasService.request(program, tables)).adapterResponse
}
/**
* Interceptor for loading of the submitted details
* @param detail submitter
* @param index submitter index
* @param data submit data
*/
public async sendDetails(detail: any, index: any, data: any) {
let details = Object.assign({ sub: true }, detail)
let subData = data[index]
let allData = {
data: subData,
viewData: details
}
this.submittDetail.next(allData)
}
/**
*
* @returns All submits
*/
public async getSubmitts() {
return (await this.sasService.request('editors/getsubmits', null))
.adapterResponse
}
/**
* Carries the fetched submit queue over to the details URL navigation,
* so the re-created submit list component does not fetch it again.
*/
public handoffSubmits(fromsas: Array<any> | undefined) {
this.submitsHandoff = fromsas || null
}
/**
* Returns the submit queue carried by the current navigation, if any,
* and clears it - it is only valid for the navigation that set it.
*/
public takeSubmitsHandoff(): Array<any> | null {
const fromsas = this.submitsHandoff
this.submitsHandoff = null
return fromsas
}
private libsPromise: Promise<any> | null = null
/**
*
* @returns All libraries
*/
public viewLibs() {
if (!this.libsPromise) {
this.libsPromise = this.sasService
.request('public/viewlibs', null)
.then((res: any) => res.adapterResponse)
.catch((err: any) => {
this.libsPromise = null
throw err
})
}
return this.libsPromise
}
public async refreshLibInfo(libref: string) {
const data = {
lib2refresh: [{ libref }]
}
return (await this.sasService.request('public/refreshlibinfo', data))
.adapterResponse
}
public async viewTables(lib: any) {
let tables = { SASControlTable: [{ MPLIB: lib }] }
return (await this.sasService.request('public/viewtables', tables))
.adapterResponse
}
public async viewData(libDataset: any, filter_pk: any) {
let tables = {
SASControlTable: [{ LIBDS: libDataset, FILTER_RK: filter_pk }]
}
return (await this.sasService.request('public/viewdata', tables))
.adapterResponse
}
public async viewDataSearch(
searchVal: string,
numeric: boolean = false,
libDataset: any,
filter_pk: any
) {
let SEARCHTYPE = numeric ? 'NUM' : 'CHAR'
let tables = {
SASControlTable: [
{
SEARCHTYPE: searchVal.length > 0 ? SEARCHTYPE : 'NONE',
SEARCHVAL: searchVal,
LIBDS: libDataset,
FILTER_RK: filter_pk
}
]
}
return (await this.sasService.request('public/viewdata', tables))
.adapterResponse
}
public async getXLMapRules(id: string) {
const tables = {
getxlmaps_in: [{ XLMAP_ID: id }]
}
return (await this.sasService.request('editors/getxlmaps', tables))
.adapterResponse
}
public async showDiffs(tableData: any, tableName: string, program: string) {
let tables: any = {}
tables[tableName] = [tableData]
return (
await this.sasService.request(program, tables, {
useComputeApi: null // Using WEB APPROACH as a temporary workaround until VIYA JES API is fixed. For other server types then VIYA this is not applicable
})
).adapterResponse
}
public async rejecting(tableData: any, tableName: string, program: string) {
let tables: any = {}
tables[tableName] = [tableData]
return (
await this.sasService.request(program, tables, {
useComputeApi: null // Using WEB APPROACH as a temporary workaround until VIYA JES API is fixed. For other server types then VIYA this is not applicable
})
).adapterResponse
}
public async approveTable(
tableData: any,
tableName: string,
program: string
) {
let tables: any = {}
tables[tableName] = [tableData]
return (await this.sasService.request(program, tables)).adapterResponse
}
public async getHistory(tableData: any, tableName: string, program: string) {
let tables: any = {}
tables[tableName] = [tableData]
return (await this.sasService.request(program, tables)).adapterResponse
}
setQueryVariables(dataset: string, cols: any) {
let columnsData: any = { data: { cols: cols }, libds: dataset }
this.columns.next(columnsData)
}
/**
* Column metadata for the filter picker, read from the service.
*
* The picker's variable list normally comes from the columns that arrived
* with the table (see setQueryVariablesFromTable). This is the fallback for
* when there is no table - public/getcols reads the metadata only.
*
* public/getcols speaks the SAS vocabulary: TYPE is C/N and DDTYPE separates
* DATE, DATETIME and TIME. The picker keys its operator set (query.component),
* its variable highlight and its value quoting (sas-store) on the client
* vocabulary instead - TYPE is exactly 'num' or 'char' - which
* mergeColsRules derives from $dataFormats on the normal path. That pass
* never runs for this fallback, so map TYPE here, with the same rule: the
* date and time types are numeric.
*/
public async getCols(libds: string) {
const tables = { iwant: [{ libds: libds }] }
const res = await this.sasService.request('public/getcols', tables)
const cols = res.adapterResponse.cols || []
return cols.map((col: any) => ({
...col,
TYPE:
['DATE', 'DATETIME', 'TIME'].includes(col.DDTYPE) || col.TYPE === 'N'
? 'num'
: 'char'
}))
}
/**
* Feeds the filter picker its variable list.
*
* `cols` is the column metadata that came back with the table, which costs
* nothing extra and is the normal path. When the table was not returned - the
* EDIT/VIEW row and cell limits abort the request before the data arrives -
* that list is empty, which is what makes "please filter and try again"
* unactionable: the picker has nothing to offer. Fall back to
* public/getcols, which reads the column metadata without the rows.
*/
public async setQueryVariablesFromTable(libds: string, cols: any[]) {
if (cols && cols.length > 0) {
this.setQueryVariables(libds, cols)
return
}
const fallback = await this.getCols(libds)
/*
public/getcols emits an empty cols table when the dataset has no
metadata. Publishing that would push an empty array into the filter
picker, whose subscriber reads cols[0].NAME - the throw is caught, but
the picker is left half-updated (this.cols assigned, the filter.cols
assignment below the throwing line never run). There is nothing to
offer, so leave the picker as it was.
*/
if (!fallback || fallback.length === 0) return
this.setQueryVariables(libds, fallback)
}
public async getChangeInfo(tableId: any) {
let obj = { TABLE: tableId }
let table = { SASControlTable: [obj] }
return (await this.sasService.request('public/getchangeinfo', table))
.adapterResponse
}
public async getQueryValues(
variable: string,
dataset: string,
filterQuery: any[],
STARTROW?: number,
ROWS?: number
) {
let tables: any = {
iwant: [
{
libds: dataset,
col: variable,
...(STARTROW && { STARTROW: STARTROW }), //add only if present
...(ROWS && { ROWS: ROWS }) //add only if present
}
]
}
if (filterQuery.length > 0) {
tables.FILTERQUERY = filterQuery
}
return (
await this.sasService
.request('public/getcolvals', tables)
.catch((er: any) => {
throw er
})
).adapterResponse
}
public async saveQuery(libds: string, filterQuery: QueryClause[]) {
let tables = {
iwant: [{ filter_table: libds }],
filterquery: filterQuery
}
const res = await this.sasService.request('public/validatefilter', tables)
this.filter.next(res)
return res.adapterResponse
}
public async openTable(tableId: string) {
let tables = { iwant: [{ table_id: tableId }] }
return (await this.sasService.request('auditors/getstagetable', tables))
.adapterResponse
}
public checkOperator(operator: any, value: any, type: string) {
let val
let operators = ['=', '>', '<', '<=', '>=', 'ne']
switch (operator) {
case 'BETWEEN':
if (value instanceof Array) {
if (value[0] !== '' && value[1] !== '') {
val = value[0] + ' AND ' + value[1]
} else if (value[0] !== '' && value[1] === '') {
value[1] = '.'
val = value[0] + ' AND ' + value[1]
} else if (value[0] === '' && value[1] !== '') {
value[0] = '.'
val = value[0] + ' AND ' + value[1]
} else {
value[0] = '.'
value[1] = '.'
val = value[0] + ' AND ' + value[1]
}
}
break
case 'IN':
case 'NOT IN':
let arr = []
if (typeof value !== 'undefined') {
for (let ind = 0; ind < value.length; ind++) {
if (value[ind].checked === true) {
if (type === 'char') {
if (typeof value[ind].val === 'string') {
value[ind].val = value[ind].val.replace("'", "''")
}
arr.push("'" + value[ind].val + "'")
} else {
if (value[ind].val === null) {
value[ind].val = '.'
}
arr.push(value[ind].val)
}
}
}
}
value = '(' + arr + ')'
break
case 'CONTAINS':
operator = '?'
break
case 'NOT EQUAL':
operator = 'ne'
break
default:
break
}
if (type === 'num' && value === null) {
value = '.'
}
if (value === 'Please select value') {
value = ''
}
if (operators.indexOf(operator) !== -1 && type === 'num' && value === '') {
value = '.'
}
if (operator === 'BETWEEN') {
return { value: val, operator: operator }
} else {
return { value: value, operator: operator }
}
}
public whereClauseCreator(
clauses: any,
groupOperator: string,
libds: string
) {
let whereString: string = ''
let clausesArr = clauses.queryObj
let clausesLogic = clauses.groupLogic
let opr
let where = ''
let clauseArr = []
for (let index = 0; index < clauses.queryObj.length; index++) {
let string = ''
let clause = clauses.queryObj[index]
for (let ind = 0; ind < clause.elements.length; ind++) {
let query = clause.elements[ind]
if (ind < clause.elements.length - 1) {
opr = clause.clauseLogic
} else {
opr = ''
}
let val: any
for (let k = 0; k < query.values.length; k++) {
if (
typeof query.value === 'string' &&
typeof query.values[k].formatted === 'number'
) {
if (query.value === JSON.stringify(query.values[k].formatted)) {
val = query.values[k].unformatted
}
} else {
let dropValue =
typeof query.values[k].formatted !== 'number' &&
query.values[k].formatted !== null
? query.values[k].formatted.trim()
: query.values[k].formatted
if (query.value === dropValue) {
val = query.values[k].unformatted
}
}
let operators = ['=', '>', '<', '<=', '>=', 'ne']
if (
query.value === 'Please select value' &&
operators.indexOf(query.operator) !== -1
) {
val = ''
}
if (
query.operator === 'CONTAINS' ||
query.operator === 'LIKE' ||
query.operator === 'BEGINS_WITH'
) {
val = query.value
}
}
if (!val) {
val = query.value
}
if (query.value instanceof Array && query.operator === 'BETWEEN') {
val = []
val.push(query.value[0])
val.push(query.value[1])
for (let i = 0; i < query.values.length; i++) {
if (val[0] === query.values[i].formatted) {
val[0] = query.values[i].unformatted
}
if (val[1] === query.values[i].formatted) {
val[1] = query.values[i].unformatted
}
}
}
if (
query.value instanceof Array &&
(query.operator === 'IN' || query.operator === 'NOT IN')
) {
val = []
val = query.value
for (let i = 0; i < query.values.length; i++) {
if (val[i] && val[i].val === query.values[i].formatted) {
val[i].val = query.values[i].unformatted
}
}
}
let type = query.type
//if the value is variable, omit quotes in the 'where' string
const isValueVariable = query.valueVariable
let variable = query.variable === null ? '' : query.variable
let oper = query.operator === null ? '' : query.operator
// let value = val === null ? "''" : val;
let value
value = this.checkOperator(oper, val, type).value
if (typeof value === 'string' && value[0] !== '(') {
value = value.replace("'", "''")
}
oper = this.checkOperator(oper, value, type).operator
/* A temporal value is shown as a SAS literal (eg '20DEC2053'd) rather
than the internal day / second count. Only rewrite when the composed
value is nothing but the numbers the clause is built from - a single
value, a BETWEEN pair or an IN list. The BETWEEN separator is
removed before the test because ' AND ' is not part of that character
set; a typed value that is not a number, such as a date entered by
hand, would otherwise have every digit run inside it rewritten into
an unrelated literal. */
if (
!isValueVariable &&
['DATE', 'DATETIME', 'TIME'].includes(query.ddtype) &&
/^[-0-9.,()\s]+$/.test(String(value).replace(/\s+AND\s+/gi, ' '))
) {
value = String(value).replace(
/-?\d+(?:\.\d+)?/g,
(match) =>
this.helperService.sasTemporalLiteral(match, query.ddtype) ??
match
)
}
if (type === 'char' && oper !== 'IN' && oper !== 'NOT IN') {
if (typeof value === 'undefined') {
value = ''
}
if (isValueVariable) {
value = ' ' + value + ' ' //without quotes, with spaces
} else {
value = " '" + value + "' " //with quotes and spaces
}
string = string + ' ' + variable + ' ' + oper + value + opr
} else {
if (type === 'num' && typeof value === 'undefined') {
value = '.'
}
if (typeof value === 'undefined') {
value = ''
} else {
value = ' ' + value + ' '
}
string = string + ' ' + variable + ' ' + oper + value + opr
}
}
clauseArr.push(string)
}
where = ''
let groupOper
for (let i = 0; i < clauseArr.length; i++) {
if (i < clauseArr.length - 1) {
groupOper = groupOperator
} else {
groupOper = ''
}
if (clauseArr.length === 1) {
where = clauseArr[0]
} else {
where = where + ' (' + clauseArr[i] + ') ' + groupOper
}
}
whereString = where.substr(1).slice(0, -1)
where = 'WHERE' + where + ';'
let queryClause = {
whereClause: where,
string: whereString,
obj: clauses,
libds: libds
}
this.query.next(queryClause)
return { whereClause: where, string: whereString }
}
/**
* This function creates Filter Query Table
* @param filterQuery an object of type FilterQuery
* @returns {QueryClause[]} array of QueryClause
*/
public createFilterQueryTable(filterQuery: FilterQuery): QueryClause[] {
const filterQueryClauseTable: QueryClause[] = []
filterQuery.filterGroups.forEach(
(filterGroup: FilterGroup, groupIndex: number) => {
filterGroup.filterClauses.forEach((filterClause: FilterClause) => {
/* A temporal value is sent as a SAS literal (eg '20APR2042'd) rather
than the internal day / second count, so the resolved WHERE clause
- and the FILTER_TEXT shown above the grid - carries a date. */
const temporalValue = (value: any) =>
filterClause.valueVariable
? null
: this.helperService.sasTemporalLiteral(
value,
filterClause.ddtype
)
/* The range dropdown binds the formatted label, as the value list
and the IN modal do, so each end of a BETWEEN is mapped back to
its unformatted value before the literal is built. This is the
same lookup whereClauseCreator applies for the preview - without
it the formatted string reaches sasTemporalLiteral, comes back
null, and RAW_VALUE carries an unquoted date that the backend
rejects on a numeric column. */
const unformattedValue = (value: any) => {
const match = (filterClause.values || []).find(
(entry: any) => entry.formatted === value
)
return match ? match.unformatted : value
}
let rawValue = ''
if (filterClause.operator === 'BETWEEN') {
const from = unformattedValue(filterClause.value[0])
const to = unformattedValue(filterClause.value[1])
rawValue = `${temporalValue(from) ?? from} AND ${
temporalValue(to) ?? to
}`
} else if (
filterClause.operator === 'IN' ||
filterClause.operator === 'NOT IN'
) {
filterClause.value.forEach((element: any) => {
if (element.checked) {
/* Mapped like the BETWEEN ends - whereClauseCreator normally
rewrites element.val to the unformatted count before submit,
so this is a pass-through for the usual path and a guard for
any path that reaches here without the preview having run. */
const value = unformattedValue(element.val)
const literal = temporalValue(value)
if (literal) {
rawValue += (rawValue.length > 0 ? ',' : '(') + literal
} else if (
typeof value === 'string' &&
(!isSpecialMissing(value) || filterClause.type === 'char')
) {
if (rawValue.length > 0) {
rawValue += `,'${value.replace(/'/g, "''")}'`
} else {
rawValue = `('${value.replace(/'/g, "''")}'`
}
} else {
if (rawValue.length > 0) {
rawValue += `,${value}`
} else {
rawValue = `(${value}`
}
}
}
})
if (rawValue.length > 0) {
rawValue += ')'
}
} else if (filterClause.value === '') {
if (filterClause.type === 'char') {
rawValue = `' '`
} else {
rawValue = '.'
}
} else {
const literal = temporalValue(filterClause.value)
if (literal) {
rawValue = literal
} else if (
filterClause.type === 'char' &&
!filterClause.valueVariable
) {
rawValue = `'${filterClause.value.replace(/'/g, "''")}'`
}
}
filterQueryClauseTable.push({
GROUP_LOGIC: filterQuery.groupLogic,
SUBGROUP_LOGIC: filterGroup.clauseLogic || 'AND',
SUBGROUP_ID: groupIndex,
VARIABLE_NM: filterClause.variable,
OPERATOR_NM: filterClause.operator,
RAW_VALUE: rawValue || filterClause.value
})
})
}
)
return filterQueryClauseTable
}
/**
* This helper function initializes global filter when user load the filtered url
* @param path it tells whether function is being called from viewer component or editor
* @param cols array of columns metadata
*/
public initializeGlobalFilterClause(page: string, cols: Col[]): void {
const clauses: any = {
queryObj: [],
clauseLogic: [],
groupLogic: get(globals, page).filter.query[0].GROUP_LOGIC
}
get(globals, page).filter.clauses = this.helperService.deepClone(clauses)
get(globals, page).filter.groupLogic = get(
globals,
page
).filter.query[0].GROUP_LOGIC
let subGroupLogic = ''
let group: any = { elements: [] }
let groupID = 0
get(globals, page).filter.query.forEach((obj: any) => {
const temp: any = {}
for (let i = 0; i < cols.length; i++) {
if (cols[i].NAME === obj.VARIABLE_NM) {
subGroupLogic = obj.SUBGROUP_LOGIC
temp['ddtype'] = cols[i].DDTYPE
temp['type'] = cols[i].TYPE
if (cols[i].TYPE === 'num') {
temp['operators'] = globals.operators.numOperators
} else {
temp['operators'] = globals.operators.charOperators
}
temp['logic'] = null
temp['operator'] = obj.OPERATOR_NM
temp['variable'] = obj.VARIABLE_NM
if (cols[i].TYPE === 'char') {
if (obj.OPERATOR_NM === 'IN' || obj.OPERATOR_NM === 'NOT IN') {
const newValueArray: { checked: boolean; val: any }[] = []
const valueString = obj.RAW_VALUE.slice(1, -1)
const valueArray = valueString.split("','")
if (valueArray.length === 1) {
newValueArray.push({
checked: true,
val: valueArray[0].slice(1, -1).replace(/''/g, "'")
})
} else {
valueArray.forEach((element: string, index: number) => {
if (index === 0) {
newValueArray.push({
checked: true,
val: element.slice(1).replace(/''/g, "'")
})
} else if (index === valueArray.length - 1) {
newValueArray.push({
checked: true,
val: element.slice(0, -1).replace(/''/g, "'")
})
} else {
newValueArray.push({
checked: true,
val: element.replace(/''/g, "'")
})
}
})
}
temp['value'] = this.helperService.deepClone(newValueArray)
} else {
temp['value'] = obj.RAW_VALUE.slice(1, -1).replace(/''/g, "'")
}
} else {
if (obj.OPERATOR_NM === 'IN' || obj.OPERATOR_NM === 'NOT IN') {
const newValueArray: { checked: boolean; val: any }[] = []
const valueString = obj.RAW_VALUE.slice(1, -1)
const valueArray = valueString.split(',')
valueArray.forEach((element: string, index: number) => {
newValueArray.push({
checked: true,
val: element
})
})
temp['value'] = this.helperService.deepClone(newValueArray)
} else if (obj.OPERATOR_NM === 'BETWEEN') {
const valueArray = obj.RAW_VALUE.split(' AND ')
temp['value'] = valueArray
} else {
temp['value'] = obj.RAW_VALUE
}
}
temp['values'] = []
if (groupID === obj.SUBGROUP_ID) {
group.elements.push(this.helperService.deepClone(temp))
} else {
get(globals, page).filter.clauses.queryObj.push(
this.helperService.deepClone(group)
)
get(globals, page).filter.clauses.queryObj[groupID].clauseLogic =
subGroupLogic
get(globals, page).filter.clauses.queryObj[groupID].invalidClause =
false
groupID++
group.elements = []
group.elements.push(this.helperService.deepClone(temp))
}
break
}
}
})
if (group.elements.length > 0) {
get(globals, page).filter.clauses.queryObj.push(
this.helperService.deepClone(group)
)
get(globals, page).filter.clauses.queryObj[groupID].clauseLogic =
subGroupLogic
get(globals, page).filter.clauses.queryObj[groupID].invalidClause = false
}
get(globals, page).filter.query = []
}
public async removeClause() {
let removeFlag = true
this.removeQuery.next(removeFlag)
return removeFlag
}
public specConversion(spec: any) {
let specObj: SpecObj = {}
let namesArr = []
namesArr = spec.map(function (item: any) {
return item.NAME.toUpperCase()
})
if (spec instanceof Array) {
for (let index = 0; index < namesArr.length; index++) {
if (spec[index].TYPE === 1) {
specObj[namesArr[index]] = {
colType: 'num',
colLength: spec[index].LENGTH
}
} else {
specObj[namesArr[index]] = {
colType: 'string',
colLength: spec[index].LENGTH
}
}
}
}
return specObj
}
setSubmitReady() {
let ready = true
this.setSubmit.next(ready)
}
parseFormats($dataFormats: $DataFormats) {
const formats: {
[key: string]: any
} = {}
const vars = $dataFormats.vars
for (let col of Object.keys(vars)) {
const type = vars[col].type
const length = vars[col].length
if (type === 'num') {
formats[col] = 'best.'
} else if (type === 'char') {
formats[col] = `$char${length}.`
}
}
return formats
}
}