src/app/lineage/lineage.component.ts

Metadata

Relationships

Used by

Depends on

No results matching.

Index

Properties
Methods

Constructor

constructor(licenceService: LicenceService, sasService: SasService, route: ActivatedRoute, router: Router, location: Location, eventService: EventService, loggerService: LoggerService, helperService: HelperService)
Parameters :
Name Type Optional
licenceService LicenceService No
sasService SasService No
route ActivatedRoute No
router Router No
location Location No
eventService EventService No
loggerService LoggerService No
helperService HelperService No

Methods

Public buildGraph
buildGraph(callback?: any)
Parameters :
Name Type Optional
callback any Yes
Returns : void
Public cancelRenderingGraph
cancelRenderingGraph()
Returns : void
Public collapseTreeItems
collapseTreeItems(tree: any, itemToSkip: any)
Parameters :
Name Type Optional
tree any No
itemToSkip any No
Returns : void
Public columnOnClick
columnOnClick(libColumn: any, library: any, libTable: any)
Parameters :
Name Type Optional
libColumn any No
library any No
libTable any No
Returns : void
Public constructName
constructName(extension: string)
Parameters :
Name Type Optional
extension string No
Returns : string
Public continueRenderingGraph
continueRenderingGraph()
Returns : void
Public directionText
directionText()
Returns : "FORWARD" | "REVERSE"
downloadCSV
downloadCSV()
Returns : void
downloadDot
downloadDot()
Returns : void
Async downloadPNG
downloadPNG()
Returns : any
downloadSVG
downloadSVG()
Returns : void
Public Async generateGraph
generateGraph(urlColumn?: String | undefined, urlDirection?: string, max_depth?: string)
Parameters :
Name Type Optional
urlColumn String | undefined Yes
urlDirection string Yes
max_depth string Yes
Returns : Promise<any>
Public Async generateGraphTableLevel
generateGraphTableLevel(table_id: string, urlDirection?: string, max_depth?: string)
Parameters :
Name Type Optional
table_id string No
urlDirection string Yes
max_depth string Yes
Returns : unknown
Private getDotUrl
getDotUrl()
Returns : any
Private getSVGURL
getSVGURL()
Returns : any
Public Async initData
initData()
Returns : any
Public libColumnActive
libColumnActive(libColumnUri: string)
Parameters :
Name Type Optional
libColumnUri string No
Returns : boolean
Public Async libraryOnClick
libraryOnClick(libid: string, library: any)
Parameters :
Name Type Optional
libid string No
library any No
Returns : any
Public libraryOnFilter
libraryOnFilter()
Returns : void
Public loadGlobalData
loadGlobalData()
Returns : void
Public loadMoreLibraries
loadMoreLibraries()
Returns : void
Public makeGraphLinkable
makeGraphLinkable(idlookup: any)
Parameters :
Name Type Optional
idlookup any No
Returns : void
ngAfterContentInit
ngAfterContentInit()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public onGenerateClick
onGenerateClick()
Returns : void
Public onGenerateGraphTableClick
onGenerateGraphTableClick()
Returns : void
Public onRouteChange
onRouteChange()
Returns : void
Public renderToDownload
renderToDownload(type: string)
Parameters :
Name Type Optional
type string No
Returns : void
Public resubmitWithMaxDepth
resubmitWithMaxDepth(maxDepth: number | string)
Parameters :
Name Type Optional
maxDepth number | string No
Returns : void
Public Async selectLibrary
selectLibrary($event: any, library?: any)
Parameters :
Name Type Optional
$event any No
library any Yes
Returns : any
Public Async selectTable
selectTable($event: any, libTableTree?: any)
Parameters :
Name Type Optional
$event any No
libTableTree any Yes
Returns : any
Public setGlobalData
setGlobalData()
Returns : void
Public showSvg
showSvg()
Returns : void
Public showTableSelect
showTableSelect()
Returns : void
Public Async tableOnClick
tableOnClick(tableuri: string, libTable: any, library: any, startupLoad: boolean)
Parameters :
Name Type Optional Default value
tableuri string No
libTable any No
library any No
startupLoad boolean No false
Returns : any
Public treeNodeClicked
treeNodeClicked(event: any, item: any, tree: any)
Parameters :
Name Type Optional
event any No
item any No
tree any No
Returns : void
Public treeOnFilter
treeOnFilter(array: any, arrToFilter: string)
Parameters :
Name Type Optional
array any No
arrToFilter string No
Returns : void

Properties

Public column
Type : string | undefined
Public columnsList
Type : Array<any> | undefined
Public currentLineagePathColumn
Type : string
Default value : ''
Public currentLineagePathLibTable
Type : string
Default value : ''
debounceTime
Type : number
Default value : 200
debounceTimeout
Type : any
Public flatdata
Type : Array<any>
Default value : []
Public forwardLineage
Type : boolean
Default value : false
Public graphContainer
Type : boolean
Default value : false
Public graphLoading
Type : boolean
Default value : false
Public graphRendering
Type : boolean
Default value : false
Public helperService
Type : HelperService
Public idlookup
Type : any[] | undefined
Public largeDotFileLimit
Type : number
Default value : 1000
Public largeDotFileLines
Type : number | null
Default value : null
Public lib
Type : string | null
Default value : null
Public librariesPaging
Type : boolean
Default value : false
Public librariesSearch
Type : string
Default value : ''
Public libraryList
Type : Array<any> | undefined
Public libraryTablesRef
Type : string
Default value : ''
Public limitDotDepth
Type : boolean
Default value : false
Public lineageColumnName
Type : string
Default value : ''
Public lineageTableName
Type : string
Default value : ''
Public pendingRenderDownload
Type : string | null
Default value : null
Public refreshCache
Type : boolean
Default value : false
Public router
Type : Router
Public switchFlag
Type : boolean
Default value : false
Public table
Type : string | undefined
Public tableColumnsRef
Type : string
Default value : ''
Public tableDisable
Type : unknown
Default value : false
Public tableFlag
Type : boolean
Default value : true
Public tablesList
Type : Array<any> | undefined
Public vizInput
Type : string
Default value : ''
import { Component, ViewEncapsulation } from '@angular/core'
import { Location } from '@angular/common'
import { globals } from '../_globals'
import * as d3Viz from 'd3-graphviz'
import { ActivatedRoute, Router } from '@angular/router'
import { EventService } from '../services/event.service'
import { HelperService } from '../services/helper.service'
import { SasService } from '../services/sas.service'
import * as saveSvg from 'save-svg-as-png'
import { LoggerService } from '../services/logger.service'
import { LicenceService } from '../services/licence.service'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
const moment = require('moment')

@Component({
  selector: 'app-lineage',
  styleUrls: ['./lineage.component.scss'],
  templateUrl: './lineage.component.html',
  host: {
    class: 'content-container'
  },
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class LineageComponent {
  public switchFlag: boolean = false
  public tableFlag: boolean = true

  public forwardLineage: boolean = false
  public flatdata: Array<any> = []

  public graphLoading: boolean = false
  public graphRendering: boolean = false
  public graphContainer: boolean = false
  public vizInput = ''

  public librariesPaging: boolean = false
  public libraryTablesRef: string = ''
  public libraryList: Array<any> | undefined
  public librariesSearch: string = ''
  public tableColumnsRef: string = ''
  public tablesList: Array<any> | undefined
  public columnsList: Array<any> | undefined

  public lineageTableName: string = ''
  public lineageColumnName: string = ''

  public lib: string | null = null
  public table: string | undefined
  public column: string | undefined
  public tableDisable = false
  public idlookup: any[] | undefined

  public refreshCache: boolean = false

  public currentLineagePathLibTable: string = ''
  public currentLineagePathColumn: string = ''

  public largeDotFileLines: number | null = null
  public largeDotFileLimit: number = 1000 //default is 1000

  public limitDotDepth: boolean = false

  public pendingRenderDownload: string | null = null

  constructor(
    private licenceService: LicenceService,
    private sasService: SasService,
    private route: ActivatedRoute,
    public router: Router,
    private location: Location,
    private eventService: EventService,
    private loggerService: LoggerService,
    public helperService: HelperService
  ) {}

  public showTableSelect() {
    this.tableFlag = !this.tableFlag
  }

  public async tableOnClick(
    tableuri: string,
    libTable: any,
    library: any,
    startupLoad: boolean = false
  ) {
    if (!libTable['columns']) {
      libTable['expanded'] = !libTable['expanded']
      libTable['loadingColumns'] = true
      this.table = tableuri

      this.currentLineagePathLibTable =
        libTable.LIBNAME + '.' + libTable.TABLENAME

      await this.selectTable(tableuri, libTable)

      if (!startupLoad) {
        this.onGenerateGraphTableClick()
      }
    } else {
      libTable['expanded'] = !libTable['expanded']

      if (libTable['expanded'] === true) {
        this.table = tableuri
        if (!startupLoad) {
          this.onGenerateGraphTableClick()
        }
      }
    }

    this.collapseTreeItems(library['tables'], libTable)
  }

  public async selectTable($event: any, libTableTree?: any) {
    this.columnsList = []

    let libTable = { SASControlTable: [{ tableuri: $event }] }

    await this.sasService
      .request('lineage/getmetacols', libTable)
      .then((res: RequestWrapperResponse) => {
        this.columnsList = res.adapterResponse.metacols
        if (this.columnsList && this.columnsList.length > 0) {
          // this.column = this.columnsList[0]['COLURI']

          libTableTree['columns'] = this.columnsList
          libTableTree['expanded'] = true
          libTableTree['loadingColumns'] = false
        }
      })
      .catch((err: any) => err)

    this.setGlobalData()
  }

  public loadMoreLibraries() {
    if (!this.librariesPaging) {
      this.librariesPaging = true

      this.helperService.displayLibraries(this.libraryList, true)

      this.librariesPaging = false
    }
  }

  public collapseTreeItems(tree: any, itemToSkip: any) {
    tree.forEach((item: any) => {
      if (JSON.stringify(item) !== JSON.stringify(itemToSkip)) {
        item['expanded'] = false
      }
    })
  }

  public treeNodeClicked(event: any, item: any, tree: any) {
    if (event.target.title === 'Collapse') {
      this.collapseTreeItems(tree, item)
    }
  }

  public async libraryOnClick(libid: string, library: any) {
    library['inForeground'] = true //When we programatically call this function, we make sure it is visible in the sidebar

    if (!library['tables']) {
      library['loadingTables'] = true

      await this.selectLibrary(libid, library)
    } else {
      library['expanded'] = !library['expanded']
    }

    this.collapseTreeItems(this.libraryList, library)
  }
  public async selectLibrary($event: any, library?: any) {
    this.tablesList = []
    this.columnsList = []

    let libTable = { SASControlTable: [{ liburi: $event }] }
    await this.sasService
      .request('lineage/getmetatables', libTable)
      .then((res: RequestWrapperResponse) => {
        this.tablesList = res.adapterResponse.metatables

        if (this.tablesList && this.tablesList.length > 0) {
          library['tables'] = this.tablesList
          library['expanded'] = true
        }

        this.setGlobalData()
      })
      .catch((err) => err)

    library['loadingTables'] = false
  }

  public columnOnClick(libColumn: any, library: any, libTable: any) {
    this.lib = library.LIBRARYID
    this.table = libTable.TABLEURI
    this.column = libColumn.COLURI

    this.setGlobalData()
    this.onGenerateClick()
  }

  public treeOnFilter(array: any, arrToFilter: string) {
    this.helperService.treeOnFilter(array, arrToFilter)
  }

  public libraryOnFilter() {
    this.helperService.libraryOnFilter(
      this.libraryList,
      this.librariesSearch,
      'LIBRARYNAME'
    )

    globals.lineage.librariesSearch = this.librariesSearch
  }

  public libColumnActive(libColumnUri: string) {
    if (!this.column) {
      return false
    }

    let splitedLibColumnUri = libColumnUri.split('\\')
    let splitedColumnUri = this.column.split('\\')

    return (
      splitedLibColumnUri[splitedLibColumnUri.length - 1] ==
      splitedColumnUri[splitedColumnUri.length - 1]
    )
  }

  public setGlobalData() {
    globals.lineage.libraryList = this.libraryList
    globals.lineage.tablesList = this.tablesList
    globals.lineage.columnsList = this.columnsList

    globals.lineage.lib = this.lib
    globals.lineage.table = this.table
    globals.lineage.column = this.column
  }

  public loadGlobalData() {
    this.libraryList = globals.lineage.libraryList
    this.tablesList = globals.lineage.tablesList
    this.columnsList = globals.lineage.columnsList
    this.librariesSearch = globals.lineage.librariesSearch
  }

  public resubmitWithMaxDepth(maxDepth: number | string) {
    this.cancelRenderingGraph()

    let queryParams = undefined

    if (maxDepth) {
      queryParams = {
        max_depth: maxDepth
      }
    }

    this.router.navigate([], {
      relativeTo: this.route,
      queryParams: queryParams
    })
  }

  ngOnInit(): void {
    globals.viewer.currentSelection = 'view/lineage'
    let reload = this.route.snapshot.params['reload']

    if (reload !== undefined) {
      let url = this.router.url
      if (reload === 'reload') {
        if (!localStorage.getItem('firstLoad')) {
          localStorage['firstLoad'] = true
          setTimeout(function () {
            window.location.reload()
          }, 2000)
        } else {
          localStorage.removeItem('firstLoad')
          this.location.replaceState(url.slice(0, url.lastIndexOf('/')))
        }
      } else {
        this.location.replaceState(url.slice(0, url.lastIndexOf('/')))
      }
    }

    this.initData()
  }

  public async initData() {
    let coluri = this.route.snapshot.params['coluri']
    let direction = this.route.snapshot.params['direction']
    let table_id = this.route.snapshot.params['tableid']

    if (globals.lineage.libraryList) {
      if (globals.lineage.libraryList.length > 0) {
        this.loadGlobalData()
      } else {
        await this.sasService
          .request('public/viewlibs', null)
          .then((res: RequestWrapperResponse) => {
            this.libraryList = res.adapterResponse.saslibs
            this.helperService.displayLibraries(this.libraryList)

            if (this.libraryList) {
              if (this.libraryList.length > 0) {
                this.lib = this.libraryList[0]['LIBRARYID']
              }
            }
            this.setGlobalData()
          })
          .catch((err: any) => err)
      }

      this.route.queryParams.subscribe((queryParams) => {
        this.onRouteChange()
      })

      this.route.params.subscribe((params) => {
        this.onRouteChange()
      })
    }
  }

  debounceTimeout: any
  debounceTime: number = 200 //ms
  public onRouteChange() {
    clearTimeout(this.debounceTimeout)

    this.debounceTimeout = setTimeout(() => {
      const coluri = this.route.snapshot.params['coluri']
      const direction = this.route.snapshot.params['direction']
      const table_id = this.route.snapshot.params['tableid']
      const max_depth = this.route.snapshot.queryParams['max_depth']

      if (coluri && direction) {
        this.forwardLineage = direction === 'FORWARD'
        this.column = coluri
        this.generateGraph(coluri, direction, max_depth)
      }
      if (!this.router.url.includes('column') && table_id && direction) {
        this.forwardLineage = direction === 'FORWARD'
        this.table = table_id
        this.generateGraphTableLevel(table_id, direction, max_depth)
      }
    }, this.debounceTime)
  }

  ngAfterContentInit(): void {}

  public directionText() {
    return this.forwardLineage ? 'FORWARD' : 'REVERSE'
  }

  public onGenerateClick() {
    let directionText = this.directionText()
    let parsedColumnUrl = this.column
      ? this.column.substring(this.column.indexOf('\\') + 1)
      : null

    this.router.navigateByUrl(
      '/view/lineage/column/' + parsedColumnUrl + '/' + directionText
    )
  }

  public onGenerateGraphTableClick() {
    let directionText = this.directionText()
    let table_id = this.table!.includes('\\')
      ? this.table!.split('\\')[1]
      : this.table

    this.router.navigateByUrl('/view/lineage/' + table_id + '/' + directionText)
  }

  public async generateGraphTableLevel(
    table_id: string,
    urlDirection?: string,
    max_depth?: string
  ) {
    let libTable: any = {
      SASControlTable: [
        {
          table_id: table_id,
          direction: urlDirection ? urlDirection : this.directionText()
        }
      ]
    }

    if (libTable.SASControlTable[0].table_id === 'undefined')
      this.eventService.showAbortModal(
        'generateGraphTableLevel()',
        'table_id is undefined.',
        undefined,
        'Frontend error'
      )

    if (max_depth) libTable.SASControlTable[0].max_depth = max_depth

    this.tableFlag = false
    this.switchFlag = true
    this.graphContainer = true
    this.graphLoading = true
    this.vizInput = ''

    return new Promise<void>((resolve, reject) => {
      this.sasService
        .request('lineage/fetchtablelineage', libTable)
        .then(async (res: RequestWrapperResponse) => {
          if (res.adapterResponse.flatdata.length > 0) {
            if (this.licenceService.checkLineageLimit()) {
              this.eventService.showInfoModal(
                'Notice',
                `You have reached daily maximum of lineage diagram renderings. To unlock additional diagrams, contact [email protected]`
              )
              this.router.navigateByUrl('/view/lineage')
              return
            }
          }

          if (typeof res === 'string') {
            this.vizInput = 'digraph G {SAS Error}'
            this.buildGraph()
            return
          }

          this.lineageTableName =
            res.adapterResponse.info[0].LIBREF +
            '.' +
            res.adapterResponse.info[0].TABLENAME

          let dotArray = res.adapterResponse.finalfinal
          let vizTmp: string = ''

          for (let i = 0; i < dotArray.length; i++) {
            vizTmp += unescape(dotArray[i].LINE) + '\n'
          }

          this.flatdata = res.adapterResponse.flatdata

          if (this.libraryList) {
            let libraryToSelect = this.libraryList.find((library: any) =>
              res.adapterResponse.info[0].LIBURI.toUpperCase().includes(
                library.LIBRARYID.toUpperCase()
              )
            )

            if (libraryToSelect) {
              let tableToSelect

              await this.libraryOnClick(
                libraryToSelect.LIBRARYID,
                libraryToSelect
              )

              if (libraryToSelect['tables']) {
                tableToSelect = libraryToSelect['tables'].find((table: any) =>
                  table.TABLEURI.toUpperCase().includes(
                    res.adapterResponse.info[0].TABLEID.toUpperCase()
                  )
                )

                if (tableToSelect) {
                  this.table = tableToSelect.TABLEURI

                  if (this.table) {
                    const query = this.table.replace('\\', '\\\\')

                    setTimeout(() => {
                      let tablePElement = document.querySelector(
                        `[id='${query}']`
                      )

                      if (tablePElement) {
                        tablePElement.scrollIntoView()
                      }
                    }, 1000)
                  }

                  this.tableOnClick(
                    tableToSelect.TABLEURI,
                    tableToSelect,
                    libraryToSelect,
                    urlDirection !== undefined
                  )
                }
              }

              if (libraryToSelect) {
                libraryToSelect['expanded'] = true
              }

              if (tableToSelect) {
                tableToSelect['expanded'] = true
              }
            }
          }

          this.vizInput = vizTmp ? vizTmp : 'digraph G {No Lineage Available}'
          this.vizInput = this.vizInput
            .replace(/\sds:/g, '\nds:')
            .replace(/\s\n/g, '\n')

          this.idlookup = res.adapterResponse.idlookup

          if (res.adapterResponse.finalfinal.length > this.largeDotFileLimit) {
            this.largeDotFileLines = res.adapterResponse.finalfinal.length
          } else {
            this.buildGraph()
          }

          resolve()
        })
        .catch((err: any) => {
          this.graphLoading = false
          this.graphContainer = false
        })
    })
  }

  public cancelRenderingGraph() {
    this.vizInput = 'digraph G {No Lineage Available}'

    this.largeDotFileLines = null
    this.buildGraph()
  }

  public continueRenderingGraph() {
    this.largeDotFileLines = null

    this.buildGraph(() => {
      if (this.pendingRenderDownload !== null) {
        switch (this.pendingRenderDownload) {
          case 'PNG': {
            this.downloadPNG()

            break
          }
          case 'SVG': {
            this.downloadSVG()

            break
          }
        }

        this.pendingRenderDownload = null
        this.vizInput = 'digraph G {No Lineage Available}'
        this.buildGraph()
      }
    })
  }

  public renderToDownload(type: string) {
    switch (type) {
      case 'PNG': {
        this.pendingRenderDownload = 'PNG'
        this.continueRenderingGraph()

        break
      }
      case 'SVG': {
        this.pendingRenderDownload = 'SVG'
        this.continueRenderingGraph()

        break
      }
    }
  }

  public makeGraphLinkable(idlookup: any) {
    let graphNodes: any = document.querySelectorAll('#graph .node')

    for (let node of graphNodes) {
      let metaid = node.querySelector('title').innerHTML
      let meta = idlookup.find((x: any) => x.METAID === metaid)

      let href: string

      if (meta) {
        if (meta.METATYPE === 'TABLE') {
          href = '/view/data/' + meta.METANAME
        } else {
          href = '/view/metadata/object/' + meta.METAID
        }

        node.classList.add('cursor-pointer')
        node.addEventListener('click', (event: MouseEvent) => {
          this.router.navigateByUrl(href)
        })
      }
    }
  }

  public async generateGraph(
    urlColumn?: String | undefined,
    urlDirection?: string,
    max_depth?: string
  ): Promise<any> {
    let libTable: any = {
      SASControlTable: [
        {
          coluri: urlColumn ? urlColumn : this.column,
          direction: urlDirection ? urlDirection : this.directionText(),
          refresh: this.refreshCache ? 1 : 0
        }
      ]
    }

    if (libTable.SASControlTable[0].coluri === 'undefined')
      this.eventService.showAbortModal(
        'generateGraph()',
        'coluri is undefined.',
        undefined,
        'Frontend error'
      )

    if (max_depth) libTable.SASControlTable[0].max_depth = max_depth

    this.tableFlag = false
    this.switchFlag = true
    this.graphContainer = true
    this.graphLoading = true
    this.vizInput = ''

    return new Promise<void>((resolve, reject) => {
      this.sasService
        .request('lineage/fetchcollineage', libTable)
        .then(async (res: RequestWrapperResponse) => {
          if (res.adapterResponse.flatdata.length > 0) {
            if (this.licenceService.checkLineageLimit()) {
              this.eventService.showInfoModal(
                'Notice',
                `You have reached daily maximum of lineage diagram renderings. To unlock additional diagrams, contact [email protected]`
              )
              this.router.navigateByUrl('/view/lineage')
              return
            }
          }

          if (typeof res.adapterResponse === 'string') {
            this.vizInput = 'digraph G {SAS Error}'
            this.buildGraph()
            return
          }

          this.lineageTableName =
            res.adapterResponse.info[0].LIBREF +
            '.' +
            res.adapterResponse.info[0].TABNAME
          this.lineageColumnName = res.adapterResponse.info[0].COLNAME

          this.idlookup = res.adapterResponse.idlookup

          let dotArray = res.adapterResponse.fromsas
          let vizTmp: string = ''
          for (let i = 0; i < dotArray.length; i++) {
            vizTmp += unescape(dotArray[i].STRING) + '\n'
          }

          this.vizInput = vizTmp ? vizTmp : 'digraph G {No Lineage Available}'
          this.vizInput = this.vizInput
            .replace(/\sds:/g, '\nds:')
            .replace(/\s\n/g, '\n')

          this.flatdata = res.adapterResponse.flatdata

          if (this.libraryList) {
            let libraryToSelect = this.libraryList.find((library: any) =>
              res.adapterResponse.info[0]?.LIBURI?.toUpperCase()?.includes(
                library?.LIBRARYID?.toUpperCase()
              )
            )

            let tableToSelect: any

            if (libraryToSelect) {
              await this.libraryOnClick(
                libraryToSelect.LIBRARYID,
                libraryToSelect
              )

              if (libraryToSelect['tables']) {
                tableToSelect = libraryToSelect['tables'].find(
                  (table: any) =>
                    table.TABLEURI === res.adapterResponse.info[0].TABURI
                )

                if (tableToSelect) {
                  this.tableOnClick(
                    tableToSelect.TABLEURI,
                    tableToSelect,
                    libraryToSelect,
                    true
                  ).then(() => {
                    let tableUri = tableToSelect.TABLEURI

                    if (tableUri) {
                      const query = tableUri.replace('\\', '\\\\')
                      let tablePElement = document.querySelector(
                        `[id='${query}']`
                      )

                      setTimeout(() => {
                        if (tablePElement) {
                          this.loggerService.log(
                            'libraryToSelect',
                            libraryToSelect
                          )
                          tablePElement.scrollIntoView()
                        }
                      }, 1000)
                    }
                  })
                }
              }

              if (libraryToSelect) {
                libraryToSelect['expanded'] = true
              }

              if (tableToSelect) {
                tableToSelect['expanded'] = true
              }
            }
          }

          if (res.adapterResponse.fromsas.length > this.largeDotFileLimit) {
            this.largeDotFileLines = res.adapterResponse.fromsas.length
          } else {
            this.buildGraph()
          }

          resolve()
        })
        .catch((err: any) => {
          this.graphLoading = false
          this.graphContainer = false
        })
    })
  }

  private getSVGURL() {
    let svg: any = document.getElementById('graph')
    let serializer = new XMLSerializer()
    let svg_blob = new Blob([serializer.serializeToString(svg)], {
      type: 'image/svg+xml'
    })
    return URL.createObjectURL(svg_blob)
  }

  downloadSVG() {
    d3Viz.graphviz('#graph').resetZoom()

    let downloadLink = document.createElement('a')
    downloadLink.href = this.getSVGURL()
    downloadLink.download = this.constructName('svg')
    downloadLink.click()
  }

  async downloadPNG() {
    d3Viz.graphviz('#graph').resetZoom()

    saveSvg.saveSvgAsPng(
      document.querySelector('#graph svg'),
      this.constructName('png')
    )
  }

  downloadCSV() {
    let data = this.flatdata

    const replacer = (key: any, value: any) => (value === null ? '' : value)
    const header = Object.keys(data[0])
    let csv = data.map((row: any) =>
      header
        .map((fieldName) => JSON.stringify(row[fieldName], replacer))
        .join(',')
    )
    csv.unshift(header.join(','))
    let csvArray = csv.join('\r\n')

    var a = document.createElement('a')
    var blob = new Blob([csvArray], { type: 'text/csv' })

    var url = window.URL.createObjectURL(blob)
    a.href = url
    a.download = this.constructName('csv')
    a.click()
    window.URL.revokeObjectURL(url)
  }

  private getDotUrl() {
    let data = this.vizInput
    let dot_blob = new Blob([data], { type: 'text/plain' })
    return window.URL.createObjectURL(dot_blob)
  }

  downloadDot() {
    let downloadLink = document.createElement('a')
    downloadLink.href = this.getDotUrl()
    downloadLink.download = this.constructName('txt')
    downloadLink.click()
  }

  public showSvg() {
    window.open(this.getSVGURL(), '_blank')
  }

  public buildGraph(callback?: any) {
    this.eventService.closeSidebar()

    this.graphLoading = false
    this.graphRendering = true

    setTimeout(() => {
      d3Viz
        .graphviz('#graph')
        .zoom(true)
        .addImage(
          'https://datacontroller.io/wp-content/uploads/2020/01/fc1.png',
          '30px',
          '30px'
        )
        .renderDot(this.vizInput, () => {
          this.graphRendering = false

          if (!!this.idlookup) {
            this.makeGraphLinkable(this.idlookup)
          }

          if (callback) {
            callback()
          }
        })
    }, 100)
  }

  public constructName(extension: string) {
    let libraryName = ''
    let tableName = ''
    let columnName = ''
    let date = moment().format('YYMMDD_HHmm')
    if (this.libraryList && this.libraryList.length) {
      let library: any = this.libraryList.find(
        (e: any) => e.LIBRARYID == this.lib
      )
      if (library) {
        libraryName = `_${library.LIBRARYNAME}`
      }
    }
    if (this.tablesList && this.tablesList.length) {
      let table: any = this.tablesList.find(
        (e: any) => e.TABLEURI == this.table
      )
      if (table) {
        tableName = `_${table.TABLENAME}`
      }
    }
    if (this.columnsList && this.columnsList.length) {
      let column: any = this.columnsList.find(
        (e: any) => e.COLURI == this.column
      )
      if (column) {
        columnName = `_${column.COLNAME}`
      }
    }
    return `${this.directionText()}${libraryName}${tableName}${columnName}_${date}.${extension}`
  }
}
<app-sidebar (scrolledToBottom)="loadMoreLibraries()">
  <clr-tree>
    @if (libraryList) {
      <clr-tree-node class="search-node">
        <div class="tree-search-wrapper">
          <input
            appStealFocus
            clrInput
            #searchLibTreeInput
            placeholder="Libraries"
            name="input"
            [(ngModel)]="librariesSearch"
            (keyup)="libraryOnFilter()"
            autocomplete="off"
          />
          @if (searchLibTreeInput.value.length < 1) {
            <clr-icon shape="search"></clr-icon>
          }
          @if (searchLibTreeInput.value.length > 0) {
            <clr-icon
              (click)="librariesSearch = ''; libraryOnFilter()"
              shape="times"
            ></clr-icon>
          }
        </div>
      </clr-tree-node>
    }

    @for (library of libraryList; track library) {
      @if (!library['hidden'] && library['inForeground']) {
        <clr-tree-node
          (click)="treeNodeClicked($event, library, libraryList)"
          [(clrExpanded)]="library['expanded']"
          [clrLoading]="library['loadingTables'] && !library.tables"
          [class.clr-expanded]="library['expanded']"
        >
          <p
            (click)="
              lib = library.LIBRARYID; libraryOnClick(lib || '', library)
            "
            class="m-0 cursor-pointer"
          >
            <clr-icon shape="rack-server"></clr-icon>
            {{ library.LIBRARYNAME }}
          </p>
          @if (library['tables']) {
            <clr-tree-node class="search-node">
              <div class="tree-search-wrapper">
                <input
                  appStealFocus
                  clrInput
                  #searchTreeInput
                  placeholder="Tables"
                  name="input"
                  [(ngModel)]="library['searchString']"
                  (keyup)="treeOnFilter(library, 'tables.TABLENAME')"
                  autocomplete="off"
                />
                @if (searchTreeInput.value.length < 1) {
                  <clr-icon shape="search"></clr-icon>
                }
                @if (searchTreeInput.value.length > 0) {
                  <clr-icon
                    (click)="
                      searchTreeInput.value = '';
                      library['searchString'] = '';
                      treeOnFilter(library, 'tables.TABLENAME')
                    "
                    shape="times"
                  ></clr-icon>
                }
              </div>
            </clr-tree-node>
          }
          @for (libTable of library['tables']; track libTable) {
            <clr-tree-node
              (click)="treeNodeClicked($event, libTable, library['tables'])"
              [(clrExpanded)]="libTable['expanded']"
              [clrLoading]="libTable['loadingColumns'] && !libTable.columns"
              [class.clr-expanded]="libTable['expanded']"
            >
              <p
                (click)="tableOnClick(libTable.TABLEURI, libTable, library)"
                [id]="libTable.TABLEURI"
                class="m-0 cursor-pointer"
              >
                <clr-icon shape="table"></clr-icon>
                {{ libTable.TABLENAME }}
              </p>
              @if (libTable['columns']) {
                <clr-tree-node class="search-node">
                  <div class="tree-search-wrapper">
                    <input
                      appStealFocus
                      clrInput
                      #searchTreeInput
                      placeholder="Columns"
                      name="input"
                      [(ngModel)]="libTable['searchString']"
                      (keyup)="treeOnFilter(libTable, 'columns.COLNAME')"
                      autocomplete="off"
                    />
                    @if (searchTreeInput.value.length < 1) {
                      <clr-icon shape="search"></clr-icon>
                    }
                    @if (searchTreeInput.value.length > 0) {
                      <clr-icon
                        (click)="
                          searchTreeInput.value = '';
                          libTable['searchString'] = '';
                          treeOnFilter(libTable, 'columns.COLNAME')
                        "
                        shape="times"
                      ></clr-icon>
                    }
                  </div>
                </clr-tree-node>
              }
              @for (libColumn of libTable['columns']; track libColumn) {
                <clr-tree-node>
                  <button
                    (click)="columnOnClick(libColumn, library, libTable)"
                    class="clr-treenode-link"
                    [class.column-active]="libColumnActive(libColumn.COLURI)"
                  >
                    <clr-icon shape="objects"></clr-icon>
                    {{ libColumn.COLNAME }}
                  </button>
                </clr-tree-node>
              }
            </clr-tree-node>
          }
        </clr-tree-node>
      }
    }
  </clr-tree>

  @if (librariesPaging) {
    <div class="w-100 text-center">
      <span class="spinner spinner-sm"> Loading... </span>
    </div>
  }
</app-sidebar>

<div class="content-area">
  <div class="card">
    @if (!column && !table) {
      <div class="no-table-selected">
        <clr-icon
          shape="warning-standard"
          size="60"
          class="is-info icon-dc-fill"
        ></clr-icon>
        <p class="text-center color-gray mt-10" cds-text="section">
          Please select a column or table
        </p>
      </div>
    }

    @if (column || table) {
      @if (!graphContainer) {
        <div class="card-header d-flex flex-column justify-content-center">
          @if (!currentLineagePathColumn && !currentLineagePathLibTable) {
            <h3 class="text-center pb-10">
              {{
                currentLineagePathColumn
                  ? currentLineagePathLibTable + '.' + currentLineagePathColumn
                  : currentLineagePathLibTable
              }}
            </h3>
          }
        </div>
      }
      <div class="card-block">
        <section
          class="form-block sw position-relative d-flex align-items-center"
        >
          <div
            class="linage-title-wrapper d-flex align-items-center font-weight-bold position-absolute"
          >
            @if (lineageTableName.length > 0) {
              <span>
                {{ lineageTableName.split('.')[0] }}.<a
                  [routerLink]="'/view/data/' + lineageTableName"
                  >{{ lineageTableName.split('.')[1] }}</a
                >{{
                  lineageColumnName.length > 0 ? '.' + lineageColumnName : ''
                }}
              </span>
            }
          </div>
          @if (graphContainer) {
            <div class="clr-col-md-12 text-center d-flex justify-content-end">
              <button
                (click)="limitDotDepth = true"
                type="button"
                class="btn btn-outline mr-5"
              >
                Limit depth
              </button>
              <!-- <button class="btn btn-outline" (click)='showSvg()'> Open in New Tab </button> -->
              <div class="btn-group direction d-block">
                <div
                  class="radio btn"
                  (click)="
                    forwardLineage = false;
                    router.url.includes('column')
                      ? onGenerateClick()
                      : onGenerateGraphTableClick()
                  "
                >
                  <input
                    type="radio"
                    name="btn-group-demo-radios"
                    [checked]="!forwardLineage"
                  />
                  <label>Backward</label>
                </div>
                <div
                  class="radio btn"
                  (click)="
                    forwardLineage = true;
                    router.url.includes('column')
                      ? onGenerateClick()
                      : onGenerateGraphTableClick()
                  "
                >
                  <input
                    type="radio"
                    name="btn-group-demo-radios"
                    [checked]="forwardLineage"
                  />
                  <label>Forward</label>
                </div>
              </div>
              <clr-dropdown class="mr-10">
                <button
                  class="btn btn-info-outline"
                  clrDropdownTrigger
                  [disabled]="!column && !table"
                >
                  Download
                  <clr-icon shape="caret down"></clr-icon>
                </button>
                <clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
                  <div (click)="downloadSVG()" clrDropdownItem>SVG</div>
                  <div (click)="downloadPNG()" clrDropdownItem>PNG</div>
                  <div (click)="downloadDot()" clrDropdownItem>Dot</div>
                  @if (flatdata) {
                    <div (click)="downloadCSV()" clrDropdownItem>CSV</div>
                  }
                </clr-dropdown-menu>
              </clr-dropdown>
              @if (column) {
                <clr-checkbox-wrapper class="d-flex align-items-center">
                  <input
                    type="checkbox"
                    (change)="generateGraph()"
                    clrCheckbox
                    name="refreshCache"
                    [(ngModel)]="refreshCache"
                  />
                  <label>Refresh Cache</label>
                </clr-checkbox-wrapper>
              }
            </div>
          }
        </section>
        @if (graphContainer) {
          <div [class.mt-2]="tableFlag">
            <div class="text-center">
              @if (graphLoading) {
                <span class="spinner"> Loading... </span>
              }
              @if (!graphLoading) {
                <div class="position-relative">
                  <div class="graph-render-spinner">
                    @if (graphRendering) {
                      <span class="spinner spinner-sm"></span>
                    }
                  </div>
                  <div id="graph"></div>
                </div>
              }
            </div>
          </div>
        }
      </div>
    }
  </div>
</div>

<clr-modal [clrModalOpen]="limitDotDepth" [clrModalClosable]="false">
  <h3 class="modal-title">Limit Depth</h3>
  <div class="modal-body">
    <p>Enter the depth number you would like to limit DOT rendering to:</p>
  </div>
  <div class="modal-footer d-flex flex-column">
    <div class="biglineage-row mb-0i">
      <input
        #maxDepthDot
        type="number"
        min="1"
        placeholder="Max depth value"
        class="clr-input max-depth-input"
      />
    </div>

    <clr-control-helper class="mb-20"
      >To render with unlimited depth, leave the input empty</clr-control-helper
    >

    <div class="biglineage-row">
      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="limitDotDepth = false"
      >
        Cancel
      </button>

      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="resubmitWithMaxDepth(maxDepthDot.value); limitDotDepth = false"
      >
        Resubmit
      </button>
    </div>
  </div>
</clr-modal>

<clr-modal
  [clrModalOpen]="largeDotFileLines !== null"
  [clrModalClosable]="false"
>
  <h3 class="modal-title">Warning</h3>
  <div class="modal-body">
    <p>
      The generated dot file has {{ largeDotFileLines }} lines. Rendering may
      take a long time, how would you like to proceed?
    </p>
  </div>
  <div class="modal-footer d-flex flex-column">
    <div class="biglineage-row">
      <p>Render anyway (this may take a long time)</p>

      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="continueRenderingGraph()"
      >
        Render
      </button>
    </div>

    <div class="biglineage-row">
      <p>Download the result (PNG & SVG require rendering)</p>

      <clr-dropdown>
        <button
          class="btn btn-sm btn-primary mt-3-negative"
          clrDropdownTrigger
          [disabled]="!column && !table"
        >
          Download
          <clr-icon shape="caret down"></clr-icon>
        </button>

        <clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
          <div (click)="renderToDownload('SVG')" clrDropdownItem>SVG</div>
          <div (click)="renderToDownload('PNG')" clrDropdownItem>PNG</div>
          <div (click)="downloadDot(); cancelRenderingGraph()" clrDropdownItem>
            Dot
          </div>
        </clr-dropdown-menu>
      </clr-dropdown>
    </div>

    <div class="biglineage-row">
      <p>Resubmit with a max-depth (specify depth)</p>

      <input
        #maxDepth
        type="number"
        min="1"
        placeholder="Max depth value"
        class="clr-input max-depth-input"
      />

      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="resubmitWithMaxDepth(maxDepth.value)"
      >
        Resubmit
      </button>
    </div>

    <div class="biglineage-row">
      <p>Cancel this submission</p>

      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="cancelRenderingGraph()"
      >
        Cancel
      </button>
    </div>
  </div>
</clr-modal>

./lineage.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""