src/app/shared/requests-modal/requests-modal.component.ts

Implements

OnInit

Metadata

Relationships

Used by

Depends on

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

constructor(sasService: SasService, loggerService: LoggerService, helperService: HelperService)
Parameters :
Name Type Optional
sasService SasService No
loggerService LoggerService No
helperService HelperService No

Inputs

opened
Type : boolean

Outputs

openedChange
Type : EventEmitter

Methods

Public cutAppLoc
cutAppLoc(link: string)
Parameters :
Name Type Optional
link string No
Returns : any
downloadGeneratedCode
downloadGeneratedCode(generatedCode: string)
Parameters :
Name Type Optional
generatedCode string No
Returns : void
downloadLog
downloadLog(logFile: string)
Parameters :
Name Type Optional
logFile string No
Returns : void
downloadSourceCode
downloadSourceCode(sourceCode: string)
Parameters :
Name Type Optional
sourceCode string No
Returns : void
Public formatTimestamp
formatTimestamp(timestamp: any)
Parameters :
Name Type Optional
timestamp any No
Returns : any
Public goToLogLine
goToLogLine(linkingLine: string, requestStackId: string, type: string)
Parameters :
Name Type Optional
linkingLine string No
requestStackId string No
type string No
Returns : void
Public modalOpenChange
modalOpenChange(state: any)
Parameters :
Name Type Optional
state any No
Returns : void
Public modalOpened
modalOpened()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public Async parseErrorsAndWarnings
parseErrorsAndWarnings(req: any)
Parameters :
Name Type Optional
req any No
Returns : any
Public parseLogTimestamp
parseLogTimestamp(timestamp: any)
Parameters :
Name Type Optional
timestamp any No
Returns : string
Public timestampFromNow
timestampFromNow(timestamp: any)
Parameters :
Name Type Optional
timestamp any No
Returns : string

Properties

Private _opened
Type : boolean
Default value : false
Public sasGeneratedCodeActive
Type : boolean
Default value : false
Public sasjsConfig
Type : unknown
Default value : this.sasService.getSasjsConfig()
Public sasjsRequests
Type : SASjsRequestExtended[]
Default value : []
Public sasLogActive
Type : boolean
Default value : true
Public sasSourceCodeActive
Type : boolean
Default value : false
Public serverType
Type : string
Public tablesActive
Type : boolean
Default value : false
Public workTables
Type : any

Accessors

opened
getopened()
setopened(value: boolean)
Parameters :
Name Type Optional
value boolean No
Returns : void
import {
  Component,
  EventEmitter,
  Input,
  OnInit,
  Output,
  ViewEncapsulation
} from '@angular/core'
import { SASjsRequest } from '@sasjs/adapter'
import moment from 'moment'
import { HelperService } from 'src/app/services/helper.service'
import { LoggerService } from '../../services/logger.service'
import { SasService } from '../../services/sas.service'
import { ServerType } from '@sasjs/utils/types/serverType'
interface SASjsRequestExtended extends SASjsRequest {
  parsedTimestamp?: string
  logErrors?: string[]
  logWarnings?: string[]
  selectedTable?: string
}

@Component({
  selector: 'app-requests-modal',
  templateUrl: './requests-modal.component.html',
  styleUrls: ['./requests-modal.component.scss'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class RequestsModalComponent implements OnInit {
  private _opened: boolean = false
  get opened(): boolean {
    return this._opened
  }
  @Input()
  set opened(value: boolean) {
    this._opened = value
    if (value) this.modalOpened()

    this.loggerService.log(this.sasjsRequests)
  }

  @Output() openedChange = new EventEmitter()

  public sasLogActive: boolean = true
  public sasSourceCodeActive: boolean = false
  public sasGeneratedCodeActive: boolean = false
  public tablesActive: boolean = false

  public sasjsConfig = this.sasService.getSasjsConfig()
  public serverType: string
  public sasjsRequests: SASjsRequestExtended[] = []
  public workTables: any

  constructor(
    private sasService: SasService,
    private loggerService: LoggerService,
    private helperService: HelperService
  ) {
    this.sasjsConfig = this.sasService.getSasjsConfig()
    this.serverType = this.sasService.getServerType()
  }

  ngOnInit(): void {}

  public parseLogTimestamp(timestamp: any) {
    return `${this.formatTimestamp(timestamp)} ${this.timestampFromNow(
      timestamp
    )}`
  }

  public cutAppLoc(link: string) {
    return link.replace(this.sasjsConfig.appLoc + '/', '')
  }

  public formatTimestamp(timestamp: any) {
    return moment(timestamp).format()
      ? moment(timestamp).format('dddd, MMMM Do YYYY, h:mm:ss a')
      : timestamp
  }

  public timestampFromNow(timestamp: any) {
    return moment(timestamp).format() ? ` (${moment(timestamp).fromNow()})` : ''
  }

  public modalOpenChange(state: any) {
    this.opened = state
    this.openedChange.emit(this.opened)
  }

  public modalOpened() {
    this.sasjsRequests = this.sasService.getSasRequests()

    for (let request of this.sasjsRequests) {
      this.parseErrorsAndWarnings(request)

      request.serviceLink = this.cutAppLoc(request.serviceLink)
      request.parsedTimestamp = this.parseLogTimestamp(request.timestamp)
    }
  }

  public goToLogLine(
    linkingLine: string,
    requestStackId: string,
    type: string
  ) {
    const logWrapper: HTMLElement | null = document.querySelector(
      `#${requestStackId} .log-wrapper.saslog`
    )

    if (!logWrapper) return

    if (this.serverType === 'SASVIYA') {
      // For VIYA (textContent approach)
      const logContent = logWrapper.textContent || ''
      const logLines = logContent.split('\n')
      let lineIndex = -1

      // Find the matching line index
      for (let i = 0; i < logLines.length; i++) {
        if (logLines[i].includes(linkingLine)) {
          lineIndex = i
          break
        }
      }

      if (lineIndex === -1) return

      // Create a temporary div to calculate line height
      const tempDiv = document.createElement('div')
      tempDiv.className = 'temp-line-height-calc'
      tempDiv.textContent = 'X'
      logWrapper.appendChild(tempDiv)
      const lineHeight = tempDiv.clientHeight
      logWrapper.removeChild(tempDiv)

      // Scroll to the appropriate position
      logWrapper.scrollTop = lineHeight * lineIndex

      // Create highlighting overlay at that position
      const overlay = document.createElement('div')
      overlay.className = `line-highlight-overlay ${type === 'error' ? 'error-highlight' : 'warning-highlight'}`

      // We are setting the height using a CSS variable to the HTML element
      // this does not trigger CSP errors because it's driven by JS
      overlay.classList.add('temp-height-setter')
      document.documentElement.style.setProperty(
        '--line-height',
        `${lineHeight}px`
      )
      overlay.classList.add('line-position-setter')
      document.documentElement.style.setProperty(
        '--line-top',
        `${lineHeight * lineIndex}px`
      )

      logWrapper.appendChild(overlay)

      setTimeout(() => {
        if (logWrapper.contains(overlay)) {
          logWrapper.removeChild(overlay)
        }
      }, 3000)
    } else {
      // For non-VIYA (innerHTML approach)
      const allLines: NodeListOf<Element> = document.querySelectorAll(
        `#${requestStackId} .log-wrapper.saslog font`
      )

      for (let line of Array.from(allLines)) {
        if (line.textContent?.includes(linkingLine)) {
          logWrapper.scrollTop =
            (line as HTMLElement).offsetTop - logWrapper.offsetTop
          // Use class instead of inline style
          line.classList.add('highlighted-line')

          setTimeout(() => {
            line.classList.remove('highlighted-line')
          }, 3000)
          break
        }
      }
    }
  }

  public async parseErrorsAndWarnings(req: any) {
    if (!req || !req.logFile || typeof req.logFile !== 'string') return
    if (req['logErrors'] !== undefined || req['logWarnings'] !== undefined)
      return

    let errorLines = []
    let warningLines = []

    // Create a safe version of the log content
    let logLines = req.logFile.split('\n')
    let originalLogLines = [...logLines]

    for (let i = 0; i < logLines.length; i++) {
      if (/<.*>ERROR/gm.test(logLines[i])) {
        let errorLine = logLines[i].substring(
          logLines[i].indexOf('E'),
          logLines[i].length - 1
        )
        errorLines.push(errorLine)
      } else if (/^ERROR/gm.test(logLines[i])) {
        errorLines.push(logLines[i])

        if (this.serverType !== 'SASVIYA') {
          logLines[i] = '<font class="error-line">' + logLines[i] + '</font>'
        }
      }

      if (/<.*>WARNING/gm.test(logLines[i])) {
        let warningLine = logLines[i].substring(
          logLines[i].indexOf('W'),
          logLines[i].length - 1
        )
        warningLines.push(warningLine)
      } else if (/^WARNING/gm.test(logLines[i])) {
        warningLines.push(logLines[i])

        if (this.serverType !== 'SASVIYA') {
          logLines[i] = '<font class="warning-line">' + logLines[i] + '</font>'
        }
      }
    }

    this.loggerService.log(warningLines)

    // For VIYA, store both versions of the log
    if (this.serverType === 'SASVIYA') {
      req.originalLogFile = originalLogLines.join('\n')
      req.logFileLineMap = {}

      // Create a mapping of error/warning lines to their indices
      errorLines.forEach((errorLine) => {
        for (let i = 0; i < originalLogLines.length; i++) {
          if (originalLogLines[i].includes(errorLine)) {
            if (!req.logFileLineMap.errors) req.logFileLineMap.errors = {}
            req.logFileLineMap.errors[errorLine] = i
            break
          }
        }
      })

      warningLines.forEach((warningLine) => {
        for (let i = 0; i < originalLogLines.length; i++) {
          if (originalLogLines[i].includes(warningLine)) {
            if (!req.logFileLineMap.warnings) req.logFileLineMap.warnings = {}
            req.logFileLineMap.warnings[warningLine] = i
            break
          }
        }
      })
    }

    req.logFile = logLines.join('\n')
    req.logErrors = errorLines
    req.logWarnings = warningLines
  }

  downloadLog(logFile: string) {
    const timestamp = new Date().valueOf()
    this.helperService.downloadTextFile(`logFile-${timestamp}`, logFile)
  }

  downloadSourceCode(sourceCode: string) {
    const timestamp = new Date().valueOf()
    this.helperService.downloadTextFile(`sourceCode-${timestamp}`, sourceCode)
  }

  downloadGeneratedCode(generatedCode: string) {
    const timestamp = new Date().valueOf()
    this.helperService.downloadTextFile(
      `generatedCode-${timestamp}`,
      generatedCode
    )
  }
}
<clr-modal
  [clrModalOpen]="opened"
  (clrModalOpenChange)="modalOpenChange($event)"
  clrModalSize="xl"
  class="requests-modal"
>
  <h3 class="modal-title">
    Request History
    <p class="mt-0 d-inline-block">App Location: {{ sasjsConfig.appLoc }}</p>
  </h3>
  <div class="modal-body h-70vh">
    @if (sasjsRequests.length < 1) {
      <p class="no-reqs">No requests made</p>
    }

    @if (opened && sasjsRequests.length > 0) {
      <clr-stack-view>
        @for (programLog of sasjsRequests; track programLog; let i = $index) {
          <clr-stack-block [id]="'request_' + i" [clrStackViewLevel]="1">
            <clr-stack-label>
              {{ programLog.serviceLink }}
              <span class="ml-10">
                @if (programLog.logWarnings?.length! > 0) {
                  <span>
                    <clr-icon
                      class="mb-5 color-green"
                      shape="exclamation-circle"
                    ></clr-icon>
                  </span>
                }
                @if (programLog.logErrors?.length! > 0) {
                  <span>
                    <clr-icon
                      class="mb-5 color-red"
                      shape="exclamation-triangle"
                    ></clr-icon>
                  </span>
                }
              </span>
            </clr-stack-label>
            <clr-stack-content>{{
              programLog.parsedTimestamp
            }}</clr-stack-content>
            <clr-stack-block class="content">
              <clr-stack-content>
                <clr-tabs>
                  <clr-tab>
                    <button clrTabLink>SAS Log</button>
                    <ng-template [(clrIfActive)]="sasLogActive">
                      <clr-tab-content>
                        <clr-tree>
                          @if (programLog.logErrors) {
                            <clr-tree-node class="err-links">
                              <clr-icon
                                class="color-red"
                                shape="exclamation-triangle"
                              ></clr-icon>
                              Errors ({{ programLog.logErrors.length! }})
                              @for (
                                err of programLog.logErrors || [];
                                track err
                              ) {
                                <clr-tree-node
                                  (click)="
                                    goToLogLine(err, 'request_' + i, 'error')
                                  "
                                >
                                  <button class="clr-treenode-link">
                                    {{ err }}
                                  </button>
                                </clr-tree-node>
                              }
                            </clr-tree-node>
                          }
                          @if (programLog.logWarnings) {
                            <clr-tree-node class="err-links">
                              <clr-icon
                                class="color-green"
                                shape="exclamation-circle"
                              ></clr-icon>
                              Warnings ({{ programLog.logWarnings.length! }})
                              @for (
                                warning of programLog.logWarnings || [];
                                track warning
                              ) {
                                <clr-tree-node
                                  (click)="
                                    goToLogLine(
                                      warning,
                                      'request_' + i,
                                      'warning'
                                    )
                                  "
                                >
                                  <button class="clr-treenode-link">
                                    {{ warning }}
                                  </button>
                                </clr-tree-node>
                              }
                            </clr-tree-node>
                          }
                        </clr-tree>
                        @if (serverType !== 'SASVIYA') {
                          <div
                            class="log-wrapper saslog"
                            [innerHTML]="programLog.logFile"
                          ></div>
                        }
                        @if (serverType === 'SASVIYA') {
                          <div
                            class="log-wrapper saslog viya"
                            [textContent]="programLog.logFile"
                          ></div>
                        }
                        @if (programLog.logFile.length > 0) {
                          <button
                            (click)="downloadLog(programLog.logFile)"
                            type="button"
                            class="btn btn-link"
                          >
                            Download
                          </button>
                        }
                      </clr-tab-content>
                    </ng-template>
                  </clr-tab>
                  <clr-tab>
                    <button clrTabLink>SAS Source Code</button>
                    <ng-template [(clrIfActive)]="sasSourceCodeActive">
                      <clr-tab-content>
                        <div class="log-wrapper">
                          {{ programLog.sourceCode }}
                        </div>
                        @if (programLog.sourceCode.length > 0) {
                          <button
                            (click)="downloadSourceCode(programLog.sourceCode)"
                            type="button"
                            class="btn btn-link"
                          >
                            Download
                          </button>
                        }
                      </clr-tab-content>
                    </ng-template>
                  </clr-tab>
                  <clr-tab>
                    <button clrTabLink>SAS Generated Code</button>
                    <ng-template [(clrIfActive)]="sasGeneratedCodeActive">
                      <clr-tab-content>
                        <div class="log-wrapper">
                          {{ programLog.generatedCode }}
                        </div>
                        @if (programLog.generatedCode.length > 0) {
                          <button
                            (click)="
                              downloadGeneratedCode(programLog.generatedCode)
                            "
                            type="button"
                            class="btn btn-link"
                          >
                            Download
                          </button>
                        }
                      </clr-tab-content>
                    </ng-template>
                  </clr-tab>
                  @if (programLog.SASWORK) {
                    <clr-tab>
                      <clr-dropdown
                        [clrCloseMenuOnItemClick]="true"
                        class="work-tables-dropdown"
                        clrTabLink
                      >
                        <button clrDropdownTrigger>
                          SAS Work Tables
                          <clr-icon shape="caret down"></clr-icon>
                        </button>
                        <clr-dropdown-menu *clrIfOpen>
                          @for (
                            table of programLog.SASWORK | keyvalue;
                            track table
                          ) {
                            <div
                              (click)="
                                programLog.selectedTable = $any(table).key
                              "
                              [class.selected]="
                                programLog.selectedTable === table.key
                              "
                              class="dropdown-item"
                              clrDropdownItem
                            >
                              {{ table.key }}
                            </div>
                          }
                        </clr-dropdown-menu>
                      </clr-dropdown>
                      <ng-template [(clrIfActive)]="tablesActive">
                        <clr-tab-content>
                          @if (programLog.selectedTable) {
                            <p class="mt-10">
                              <strong
                                >{{ programLog.selectedTable }} (first 10 rows)
                              </strong>
                            </p>
                          }
                          <div class="min-h-50vh">
                            @if (programLog.selectedTable) {
                              <table class="table mt-10">
                                <thead>
                                  <tr>
                                    @for (
                                      column of programLog.SASWORK[
                                        programLog.selectedTable
                                      ].first10rows[0] | keyvalue;
                                      track column
                                    ) {
                                      <th>
                                        {{ column.key }}
                                      </th>
                                    }
                                  </tr>
                                </thead>
                                <tbody>
                                  @for (
                                    row of programLog.SASWORK[
                                      programLog.selectedTable
                                    ].first10rows;
                                    track row
                                  ) {
                                    <tr>
                                      @for (
                                        column of programLog.SASWORK[
                                          programLog.selectedTable
                                        ].first10rows[0] | keyvalue;
                                        track column
                                      ) {
                                        <td>
                                          {{ row[$any(column).key] }}
                                        </td>
                                      }
                                    </tr>
                                  }
                                </tbody>
                              </table>
                            }
                          </div>
                        </clr-tab-content>
                      </ng-template>
                    </clr-tab>
                  }
                </clr-tabs>
              </clr-stack-content>
            </clr-stack-block>
          </clr-stack-block>
        }
      </clr-stack-view>
    }
  </div>
</clr-modal>

./requests-modal.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""