src/app/services/helper.service.ts

Metadata

Index

Properties
Methods

Constructor

constructor(sasService: SasService)
Parameters :
Name Type Optional
sasService SasService No

Methods

Public addLeadingZero
addLeadingZero(value: string | number)
Parameters :
Name Type Optional
value string | number No
Returns : string
Public convertArrayValues
convertArrayValues(array: any[], type: "string" | "number")
Parameters :
Name Type Optional
array any[] No
type "string" | "number" No
Returns : [] | []
Public convertJsDateToSasDate
convertJsDateToSasDate(jsDate: string | Date, unit: string)

Converts a JavaScript date object to a SAS Date or Datetime, given the logic below:

A JS Date contains the number of milliseconds since 01/01/1970 A SAS Date contains the number of days since 01/01/1960 A SAS Datetime contains the number of seconds since 01/01/1960

Parameters :
Name Type Optional Default value Description
jsDate string | Date No

JS Date to be converted. The type is instance of Date

unit string No 'days'

Unit in which to return the SAS Date / datetime, eg sasdate | sasdatetime

Returns : number

SAS Date value based on unit param

Public convertObjectsToArray
convertObjectsToArray(objectArray: Array, deepClone: boolean)
Parameters :
Name Type Optional Default value
objectArray Array<object> No
deepClone boolean No false
Returns : any
Public convertSasDaysToJsDate
convertSasDaysToJsDate(sasValue: number | string, unit: string)

Converts a SAS Date or Datetime to a JavaScript date object, given the logic below:

A JS Date contains the number of milliseconds since 01/01/1970 A SAS Date contains the number of days since 01/01/1960 A SAS Datetime contains the number of seconds since 01/01/1960

Parameters :
Name Type Optional Default value Description
sasValue number | string No

SAS Date or Datetime to be converted. The type could be number or `string.

unit string No 'days'

Unit from which to convert the SAS Date / Datetime, eg sasdate | sasdatetime

Returns : any

JavaScript Date object

Public debounceCall
debounceCall(time: number, callback: () => void)
Parameters :
Name Type Optional
time number No
callback function No
Returns : void
Public deepClone
deepClone(item: any)
Parameters :
Name Type Optional
item any No
Returns : any
Public deleteKeysAndHash
deleteKeysAndHash(data: any, keysToRemove: string[], clone: boolean)

Hashes the object, if array provided it first deletes the keys from object

Parameters :
Name Type Optional Default value Description
data any No

data to hash, it will be deep cloned

keysToRemove string[] No

keys to remove from data object

clone boolean No true

selecting whether to deep clone data or not. Default is true

Returns : string

hashed string

Public displayLibraries
displayLibraries(array: any, loadMore?: boolean)
Parameters :
Name Type Optional
array any No
loadMore boolean Yes
Returns : void
downloadTextFile
downloadTextFile(filename: string, text: string)
Parameters :
Name Type Optional
filename string No
text string No
Returns : void
Public isStreamingViya
isStreamingViya()
Returns : boolean
Public libraryOnFilter
libraryOnFilter(array: any, search: string, fieldToMatch: any)
Parameters :
Name Type Optional
array any No
search string No
fieldToMatch any No
Returns : void
Public metaObjectOnFilter
metaObjectOnFilter(array: any, search: string, fieldToMatch: any)
Parameters :
Name Type Optional
array any No
search string No
fieldToMatch any No
Returns : void
Public resetArrayFilter
resetArrayFilter(array: any)
Parameters :
Name Type Optional
array any No
Returns : void
Public resetLibraryForeground
resetLibraryForeground(array: any)
Parameters :
Name Type Optional
array any No
Returns : void
Private sasDateFromDays
sasDateFromDays(days: number)

ddMMMyyyy for a SAS date value (days since 1960-01-01).

Parameters :
Name Type Optional
days number No
Returns : string
Public sasTemporalLiteral
sasTemporalLiteral(value: any, ddtype: string)

Formats a SAS internal date / datetime / time value as the SAS literal a WHERE clause can carry, eg: 34322 (DATE) -> '20DEC2053'd 42 (DATETIME) -> '01JAN1960:00:00:42'dt 42 (TIME) -> '00:00:42't

The value is days since 1960-01-01 for a date, and seconds since 1960-01-01 (datetime) or since midnight (time). The date part is built arithmetically in UTC, so the day cannot shift with the browser timezone.

SAS temporal values are whole days and whole seconds, so the value is rounded to seconds first - that kills the noise a double picks up on the way here, where 1.9999999999999 is simply the second 2. A value that is genuinely fractional does not land on a second, and rounding it would silently widen the filter, so it returns null instead.

Returns null when the value is not a finite number, is not a whole day or second, or the ddtype is not temporal - callers then send the raw value.

Parameters :
Name Type Optional
value any No
ddtype string No
Returns : string | null
Private sasTimeFromSeconds
sasTimeFromSeconds(seconds: number)

hh:mm:ss for a SAS time value (whole seconds since midnight).

Parameters :
Name Type Optional
seconds number No
Returns : string
Public treeOnFilter
treeOnFilter(array: any, arrToFilter: string)
Parameters :
Name Type Optional Description
array any No

all elements in the clarity tree

arrToFilter string No

sub array in the tree to be filtered for example tables

Returns : void

Properties

Private debounceTimeout
Type : any
Public loadMoreCount
Type : number
Default value : librariesToShow
Public shownLibraries
Type : number
Default value : librariesToShow
import { Injectable } from '@angular/core'
import cloneDeep from 'lodash-es/cloneDeep'
import CryptoMD5 from 'crypto-js/md5'
import { SasService } from './sas.service'

const librariesToShow = 50

/** Month names used by a SAS date literal, eg 20APR2042. */
const sasMonths = [
  'JAN',
  'FEB',
  'MAR',
  'APR',
  'MAY',
  'JUN',
  'JUL',
  'AUG',
  'SEP',
  'OCT',
  'NOV',
  'DEC'
]

@Injectable({
  providedIn: 'root'
})
export class HelperService {
  public shownLibraries: number = librariesToShow
  public loadMoreCount: number = librariesToShow

  constructor(private sasService: SasService) {}

  /**
   * Converts a JavaScript date object to a SAS Date or Datetime, given the logic below:
   *
   * A JS Date contains the number of _milliseconds_ since 01/01/1970
   * A SAS Date contains the number of _days_ since 01/01/1960
   * A SAS Datetime contains the number of _seconds_ since 01/01/1960
   *
   * @param jsDate JS Date to be converted. The type is instance of `Date`
   * @param unit Unit in which to return the SAS Date / datetime, eg `sasdate | sasdatetime`
   * @returns SAS Date value based on `unit` param
   */
  public convertJsDateToSasDate(
    jsDate: string | Date,
    unit: string = 'days'
  ): number {
    let jsDateObject: Date
    let valueInMilliseconds: number = 0

    if (!(jsDate instanceof Date)) {
      jsDateObject = new Date(jsDate)
    } else {
      jsDateObject = jsDate
    }

    valueInMilliseconds = new Date(
      Date.UTC(
        jsDateObject.getFullYear(),
        jsDateObject.getMonth(),
        jsDateObject.getDate(),
        jsDateObject.getHours(),
        jsDateObject.getMinutes(),
        jsDateObject.getSeconds()
      )
    ).valueOf()

    const msInDay = 24 * 60 * 60 * 1000
    const msInTenYears = 315619200000 //since calculating it is not practical to get correct value, this is parsed with: new Date(Date.UTC(1960, 0, 1)).getTime()

    const sasMilliseconds = valueInMilliseconds + msInTenYears

    switch (unit) {
      case 'days': {
        let valueInDays = sasMilliseconds / msInDay

        valueInDays = Math.abs(valueInDays)
        valueInDays = Math.floor(valueInDays)

        return valueInDays
      }
      case 'seconds': {
        return sasMilliseconds / 1000
      }
    }

    return 0
  }

  /**
   * Converts a SAS Date or Datetime to a JavaScript date object, given the logic below:
   *
   * A JS Date contains the number of _milliseconds_ since 01/01/1970
   * A SAS Date contains the number of _days_ since 01/01/1960
   * A SAS Datetime contains the number of _seconds_ since 01/01/1960
   *
   * @param sasValue SAS Date or Datetime to be converted. The type could be `number` or `string.
   * @param unit Unit from which to convert the SAS Date / Datetime, eg `sasdate | sasdatetime`
   * @returns JavaScript Date object
   */
  public convertSasDaysToJsDate(
    sasValue: number | string,
    unit: string = 'days'
  ) {
    const msInDay = 24 * 60 * 60 * 1000
    const msNegativeTenYears = -315619200000 //since calculating it is not practical to get correct value, this is parsed with: new Date(Date.UTC(1960, 0, 1)).getTime()

    if (typeof sasValue !== 'number') sasValue = parseFloat(sasValue)

    if (unit === 'seconds') {
      let sasMs = sasValue * 1000

      let jsMs = msNegativeTenYears + sasMs

      let timezoneOffsetMs = new Date(jsMs).getTimezoneOffset() * 60 * 1000

      jsMs += timezoneOffsetMs

      return new Date(jsMs)
    }

    return new Date(msNegativeTenYears + sasValue * msInDay)
  }

  /**
   *
   * @param array all elements in the clarity tree
   * @param arrToFilter sub array in the tree to be filtered for example `tables`
   */
  public treeOnFilter(array: any, arrToFilter: string) {
    let search = array['searchString'] ? array['searchString'] : ''
    let arrToFilterArray = arrToFilter.split('.')[0]
    let arrToFilterField = arrToFilter.split('.')[1]

    let arrToFilterAll = arrToFilterArray + 'All'

    if (array[arrToFilterArray]) {
      if (!array[arrToFilterAll]) {
        array[arrToFilterAll] = this.deepClone(array[arrToFilterArray])
      }

      array[arrToFilterArray] = this.deepClone(array[arrToFilterAll])

      if (search.length > 0) {
        if (arrToFilterField) {
          array[arrToFilterArray] = array[arrToFilterArray].filter(
            (item: any) => {
              return item[arrToFilterField]
                .toLowerCase()
                .includes(search.toLowerCase())
            }
          )
        } else {
          array[arrToFilterArray] = array[arrToFilterArray].filter(
            (item: any) => {
              return item.toLowerCase().includes(search.toLowerCase())
            }
          )
        }
      }
    }
  }

  public libraryOnFilter(array: any, search: string, fieldToMatch: any) {
    if (search.length > 0) {
      array.forEach((item: any) => {
        if (!item[fieldToMatch].toLowerCase().includes(search.toLowerCase())) {
          item['hidden'] = true
          item['inForeground'] = false
        } else {
          item['hidden'] = false
          item['inForeground'] = true
        }
      })
    } else {
      this.resetArrayFilter(array)
      this.displayLibraries(array)
    }
  }

  public displayLibraries(array: any, loadMore?: boolean) {
    if (loadMore) {
      this.shownLibraries += this.loadMoreCount
    } else {
      this.shownLibraries = librariesToShow

      this.resetLibraryForeground(array)
    }

    for (let index = 0; index < array.length; index++) {
      if (index === this.shownLibraries) {
        break
      }

      array[index]['inForeground'] = true
    }
  }

  public resetLibraryForeground(array: any) {
    for (let index = 0; index < array.length; index++) {
      array[index]['inForeground'] = false
    }
  }

  public metaObjectOnFilter(array: any, search: string, fieldToMatch: any) {
    // array = this.deepClone(arrayAll);

    if (search.length > 0) {
      this.resetArrayFilter(array)
      array.forEach((item: any) => {
        if (!item[fieldToMatch].toLowerCase().includes(search.toLowerCase())) {
          item['hidden'] = true
        }
      })
    } else {
      this.resetArrayFilter(array)
    }
  }

  public resetArrayFilter(array: any) {
    array.forEach((item: any) => {
      item['hidden'] = false

      if (item['inForeground']) {
        delete item['inForeground']
      }
    })
  }

  public convertObjectsToArray(
    objectArray: Array<object>,
    deepClone: boolean = false
  ) {
    if (deepClone) {
      objectArray = this.deepClone(objectArray)
    }

    return objectArray.map((obj: any) => {
      return Object.keys(obj).map((key) => {
        return obj[key]
      })
    })
  }

  /**
   * Formats a SAS internal date / datetime / time value as the SAS literal a
   * WHERE clause can carry, eg:
   *   34322 (DATE)     -> '20DEC2053'd
   *   42    (DATETIME) -> '01JAN1960:00:00:42'dt
   *   42    (TIME)     -> '00:00:42't
   *
   * The value is days since 1960-01-01 for a date, and seconds since
   * 1960-01-01 (datetime) or since midnight (time).  The date part is built
   * arithmetically in UTC, so the day cannot shift with the browser timezone.
   *
   * SAS temporal values are whole days and whole seconds, so the value is
   * rounded to seconds first - that kills the noise a double picks up on the
   * way here, where 1.9999999999999 is simply the second 2.  A value that is
   * genuinely fractional does not land on a second, and rounding it would
   * silently widen the filter, so it returns null instead.
   *
   * Returns null when the value is not a finite number, is not a whole day or
   * second, or the ddtype is not temporal - callers then send the raw value.
   */
  public sasTemporalLiteral(value: any, ddtype: string): string | null {
    const kind = (ddtype ?? '').toString().toUpperCase()
    if (kind !== 'DATE' && kind !== 'DATETIME' && kind !== 'TIME') return null
    if (value === null || value === undefined || value === '') return null

    const num = typeof value === 'number' ? value : Number(String(value).trim())
    if (!isFinite(num)) return null

    /* Within the precision of a double, is this a whole second? */
    const seconds = Math.round(num * 1e8) / 1e8
    if (!Number.isInteger(seconds)) return null

    if (kind === 'TIME') return `'${this.sasTimeFromSeconds(seconds)}'t`

    const days = kind === 'DATETIME' ? Math.floor(seconds / 86400) : seconds
    const datePart = this.sasDateFromDays(days)

    if (kind === 'DATE') return `'${datePart}'d`

    return `'${datePart}:${this.sasTimeFromSeconds(seconds - days * 86400)}'dt`
  }

  /** `ddMMMyyyy` for a SAS date value (days since 1960-01-01). */
  private sasDateFromDays(days: number): string {
    const date = new Date(Date.UTC(1960, 0, 1) + days * 86400000)

    return (
      this.addLeadingZero(date.getUTCDate()) +
      sasMonths[date.getUTCMonth()] +
      date.getUTCFullYear()
    )
  }

  /** `hh:mm:ss` for a SAS time value (whole seconds since midnight). */
  private sasTimeFromSeconds(seconds: number): string {
    return (
      this.addLeadingZero(Math.floor(seconds / 3600)) +
      ':' +
      this.addLeadingZero(Math.floor((seconds % 3600) / 60)) +
      ':' +
      this.addLeadingZero(seconds % 60)
    )
  }

  public addLeadingZero(value: string | number): string {
    if (typeof value !== 'string') value = value.toString()

    return value.length < 2 ? '0' + value : value
  }

  /**
   * Hashes the object, if array provided it first deletes the keys from object
   * @param data data to hash, it will be deep cloned
   * @param keysToRemove keys to remove from data object
   * @param clone selecting whether to deep clone data or not. Default is true
   * @returns hashed string
   */
  public deleteKeysAndHash(
    data: any,
    keysToRemove: string[],
    clone: boolean = true
  ): string {
    const dataToHash = clone ? this.deepClone(data) : data

    for (let key of keysToRemove) {
      delete dataToHash[key]
    }

    return CryptoMD5(JSON.stringify(dataToHash)).toString()
  }

  downloadTextFile(filename: string, text: string) {
    const element = document.createElement('a')
    element.setAttribute(
      'href',
      'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
    )
    element.setAttribute('download', filename + '.txt')

    element.style.display = 'none'
    document.body.appendChild(element)

    element.click()

    document.body.removeChild(element)
  }

  public convertArrayValues(
    array: any[],
    type: 'string' | 'number'
  ): string[] | number[] {
    if (array.length < 1) return []

    switch (type) {
      case 'number': {
        return array.map((value) => value * 1)
      }
      case 'string': {
        return array.map((value) => `${value}`)
      }
    }
  }

  public isStreamingViya(): boolean {
    const serverType = this.sasService.getServerType()

    if (serverType !== 'SASVIYA') return false

    if (
      location.search.toLowerCase().includes('?_file=') &&
      location.pathname.toLowerCase().includes('/sasjobexecution')
    )
      return true

    return false
  }

  // Required type is NodeJS.Timeout
  // But NodeJS is not available in browser so we have to go with any
  private debounceTimeout: any
  public debounceCall(time: number, callback: () => void) {
    clearTimeout(this.debounceTimeout)

    this.debounceTimeout = setTimeout(callback, time)
  }

  public deepClone(item: any) {
    return cloneDeep(item)
  }
}

results matching ""

    No results matching ""