Bumps @angular/* to 21 (passing through 20 as a transient step, since vendored Clarity has no Angular-20-compatible release) and @typescript-eslint to 8.65.0 for TS 5.9 compat. Applies the required ng update migrations, including *ngIf/*ngFor/*ngSwitch -> control-flow syntax across 37 templates.
413 lines
13 KiB
HTML
413 lines
13 KiB
HTML
<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>
|