Files
dc/client/src/app/shared/requests-modal/requests-modal.component.ts

279 lines
8.1 KiB
TypeScript

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
)
}
}